Tell the host how much space it actually has
The window came up 948x952 under niri, not the 420x640 the config asked for — a tiling compositor decides that, not us. With keys at a fixed size the keypad sat in the corner of a window twice its size, and asking for Length::Fill did not help: it widens a container but not a cosmic button, which is why the keys kept their label width while the fixed heights took. So on_view now receives the space on offer, via iced's responsive, and the calculator divides it into a 4x5 grid with floors. The host doing the arithmetic is also the only version that survives a resize. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2d2278d parent: 845f2a9 modified
cosmic_ffi/Cargo.lock +1 -1 | @@ -964,7 +964,7 @@ dependencies = [ | ||
| 964 | 964 | |
| 965 | 965 | [[package]] |
| 966 | 966 | name = "cosmic_ffi" |
| 967 | -version = "0.4.0" | |
| 967 | +version = "0.5.0" | |
| 968 | 968 | dependencies = [ |
| 969 | 969 | "libcosmic", |
| 970 | 970 | ] |
| @@ -964,7 +964,7 @@ dependencies = [ | |||
| 964 | 964 | ||
| 965 | [[package]] | 965 | [[package]] |
| 966 | name = "cosmic_ffi" | 966 | name = "cosmic_ffi" |
| 967 | -version = "0.4.0" | 967 | +version = "0.5.0" |
| 968 | dependencies = [ | 968 | dependencies = [ |
| 969 | "libcosmic", | 969 | "libcosmic", |
| 970 | ] | 970 | ] |
modified
cosmic_ffi/Cargo.toml +1 -1 | @@ -1,6 +1,6 @@ | ||
| 1 | 1 | [package] |
| 2 | 2 | name = "cosmic_ffi" |
| 3 | -version = "0.4.0" | |
| 3 | +version = "0.5.0" | |
| 4 | 4 | edition = "2021" |
| 5 | 5 | |
| 6 | 6 | [lib] |
| @@ -1,6 +1,6 @@ | |||
| 1 | [package] | 1 | [package] |
| 2 | name = "cosmic_ffi" | 2 | name = "cosmic_ffi" |
| 3 | -version = "0.4.0" | 3 | +version = "0.5.0" |
| 4 | edition = "2021" | 4 | edition = "2021" |
| 5 | 5 | ||
| 6 | [lib] | 6 | [lib] |
modified
cosmic_ffi/cosmic_ffi.h +4 -1 | @@ -14,7 +14,10 @@ | ||
| 14 | 14 | /* Opaque. Valid only for the duration of the on_view call it arrived with. */ |
| 15 | 15 | typedef struct CosmicBuilder CosmicBuilder; |
| 16 | 16 | |
| 17 | -typedef void (*cosmic_on_view)(void *ctx, CosmicBuilder *builder); | |
| 17 | +/* `width`/`height` are the pixels actually available, which is not the size | |
| 18 | + requested in CosmicConfig when a tiling compositor has its own ideas. */ | |
| 19 | +typedef void (*cosmic_on_view)(void *ctx, CosmicBuilder *builder, float width, | |
| 20 | + float height); | |
| 18 | 21 | typedef void (*cosmic_on_press)(void *ctx, int32_t id); |
| 19 | 22 | |
| 20 | 23 | typedef struct { |
| @@ -14,7 +14,10 @@ | |||
| 14 | /* Opaque. Valid only for the duration of the on_view call it arrived with. */ | 14 | /* Opaque. Valid only for the duration of the on_view call it arrived with. */ |
| 15 | typedef struct CosmicBuilder CosmicBuilder; | 15 | typedef struct CosmicBuilder CosmicBuilder; |
| 16 | 16 | ||
| 17 | -typedef void (*cosmic_on_view)(void *ctx, CosmicBuilder *builder); | 17 | +/* `width`/`height` are the pixels actually available, which is not the size |
| 18 | + requested in CosmicConfig when a tiling compositor has its own ideas. */ | ||
| 19 | +typedef void (*cosmic_on_view)(void *ctx, CosmicBuilder *builder, float width, | ||
| 20 | + float height); | ||
| 18 | typedef void (*cosmic_on_press)(void *ctx, int32_t id); | 21 | typedef void (*cosmic_on_press)(void *ctx, int32_t id); |
| 19 | 22 | ||
| 20 | typedef struct { | 23 | typedef struct { |
modified
cosmic_ffi/src/lib.rs +11 -6 | @@ -15,9 +15,12 @@ use std::ffi::{c_char, c_void, CStr}; | ||
| 15 | 15 | use cosmic::iced::{Alignment, Length}; |
| 16 | 16 | use cosmic::widget; |
| 17 | 17 | |
| 18 | -/// Describes the widget tree for one frame. Only valid for the duration of | |
| 19 | -/// the `on_view` call it was handed to. | |
| 20 | -pub type OnView = Option<unsafe extern "C" fn(ctx: *mut c_void, builder: *mut Builder)>; | |
| 18 | +/// Describes the widget tree for one frame. The builder is only valid for the | |
| 19 | +/// duration of the call; `width` and `height` are the space available in | |
| 20 | +/// pixels, so the host can lay out against the real window rather than the | |
| 21 | +/// size it asked for — a tiling compositor rarely grants that size. | |
| 22 | +pub type OnView = | |
| 23 | + Option<unsafe extern "C" fn(ctx: *mut c_void, builder: *mut Builder, width: f32, height: f32)>; | |
| 21 | 24 | |
| 22 | 25 | /// Called when the button carrying `id` is pressed. |
| 23 | 26 | pub type OnPress = Option<unsafe extern "C" fn(ctx: *mut c_void, id: i32)>; |
| @@ -383,10 +386,10 @@ struct Host { | ||
| 383 | 386 | unsafe impl Send for Host {} |
| 384 | 387 | |
| 385 | 388 | impl Host { |
| 386 | - fn view(&self) -> Element { | |
| 389 | + fn view(&self, width: f32, height: f32) -> Element { | |
| 387 | 390 | let mut builder = Builder::new(); |
| 388 | 391 | if let Some(cb) = self.on_view { |
| 389 | - unsafe { cb(self.ctx, &mut builder) }; | |
| 392 | + unsafe { cb(self.ctx, &mut builder, width, height) }; | |
| 390 | 393 | } |
| 391 | 394 | builder.finish() |
| 392 | 395 | } |
| @@ -439,7 +442,9 @@ impl cosmic::Application for App { | ||
| 439 | 442 | } |
| 440 | 443 | |
| 441 | 444 | fn view(&self) -> cosmic::Element<Message> { |
| 442 | - self.flags.host.view() | |
| 445 | + // `responsive` hands the closure the space actually on offer, which is | |
| 446 | + // the only way the host can know it. | |
| 447 | + widget::responsive(|size| self.flags.host.view(size.width, size.height)).into() | |
| 443 | 448 | } |
| 444 | 449 | } |
| 445 | 450 | |
| @@ -15,9 +15,12 @@ use std::ffi::{c_char, c_void, CStr}; | |||
| 15 | use cosmic::iced::{Alignment, Length}; | 15 | use cosmic::iced::{Alignment, Length}; |
| 16 | use cosmic::widget; | 16 | use cosmic::widget; |
| 17 | 17 | ||
| 18 | -/// Describes the widget tree for one frame. Only valid for the duration of | 18 | +/// Describes the widget tree for one frame. The builder is only valid for the |
| 19 | -/// the `on_view` call it was handed to. | 19 | +/// duration of the call; `width` and `height` are the space available in |
| 20 | -pub type OnView = Option<unsafe extern "C" fn(ctx: *mut c_void, builder: *mut Builder)>; | 20 | +/// pixels, so the host can lay out against the real window rather than the |
| 21 | +/// size it asked for — a tiling compositor rarely grants that size. | ||
| 22 | +pub type OnView = | ||
| 23 | + Option<unsafe extern "C" fn(ctx: *mut c_void, builder: *mut Builder, width: f32, height: f32)>; | ||
| 21 | 24 | ||
| 22 | /// Called when the button carrying `id` is pressed. | 25 | /// Called when the button carrying `id` is pressed. |
| 23 | pub type OnPress = Option<unsafe extern "C" fn(ctx: *mut c_void, id: i32)>; | 26 | pub type OnPress = Option<unsafe extern "C" fn(ctx: *mut c_void, id: i32)>; |
| @@ -383,10 +386,10 @@ struct Host { | |||
| 383 | unsafe impl Send for Host {} | 386 | unsafe impl Send for Host {} |
| 384 | 387 | ||
| 385 | impl Host { | 388 | impl Host { |
| 386 | - fn view(&self) -> Element { | 389 | + fn view(&self, width: f32, height: f32) -> Element { |
| 387 | let mut builder = Builder::new(); | 390 | let mut builder = Builder::new(); |
| 388 | if let Some(cb) = self.on_view { | 391 | if let Some(cb) = self.on_view { |
| 389 | - unsafe { cb(self.ctx, &mut builder) }; | 392 | + unsafe { cb(self.ctx, &mut builder, width, height) }; |
| 390 | } | 393 | } |
| 391 | builder.finish() | 394 | builder.finish() |
| 392 | } | 395 | } |
| @@ -439,7 +442,9 @@ impl cosmic::Application for App { | |||
| 439 | } | 442 | } |
| 440 | 443 | ||
| 441 | fn view(&self) -> cosmic::Element<Message> { | 444 | fn view(&self) -> cosmic::Element<Message> { |
| 442 | - self.flags.host.view() | 445 | + // `responsive` hands the closure the space actually on offer, which is |
| 446 | + // the only way the host can know it. | ||
| 447 | + widget::responsive(|size| self.flags.host.view(size.width, size.height)).into() | ||
| 443 | } | 448 | } |
| 444 | } | 449 | } |
| 445 | 450 | ||
modified
cosmicnim.nimble +1 -1 | @@ -1,4 +1,4 @@ | ||
| 1 | -version = "0.4.0" | |
| 1 | +version = "0.5.0" | |
| 2 | 2 | author = "nandi" |
| 3 | 3 | description = "libcosmic behind a C ABI, driven from Nim" |
| 4 | 4 | license = "MIT" |
| @@ -1,4 +1,4 @@ | |||
| 1 | -version = "0.4.0" | 1 | +version = "0.5.0" |
| 2 | author = "nandi" | 2 | author = "nandi" |
| 3 | description = "libcosmic behind a C ABI, driven from Nim" | 3 | description = "libcosmic behind a C ABI, driven from Nim" |
| 4 | license = "MIT" | 4 | license = "MIT" |
modified
examples/calculator.nim +20 -10 | @@ -24,9 +24,10 @@ type | ||
| 24 | 24 | error: bool |
| 25 | 25 | |
| 26 | 26 | const |
| 27 | - # Keys fill their row, so the four columns split the width evenly and grow | |
| 28 | - # with the window instead of each key hugging its own label. | |
| 29 | - KeyH = 84.0 | |
| 27 | + Cols = 4 | |
| 28 | + Rows = 5 | |
| 29 | + MinKeyW = 56.0 | |
| 30 | + MinKeyH = 44.0 | |
| 30 | 31 | |
| 31 | 32 | # 0..9 are the digit keys, so their id is their value. |
| 32 | 33 | IdDot* = 10'i32 |
| @@ -151,9 +152,20 @@ proc onPress*(ctx: pointer; id: int32) {.cdecl.} = | ||
| 151 | 152 | else: |
| 152 | 153 | discard |
| 153 | 154 | |
| 154 | -proc onView(ctx: pointer; b: Builder) {.cdecl.} = | |
| 155 | +proc onView(ctx: pointer; b: Builder; width, height: cfloat) {.cdecl.} = | |
| 155 | 156 | let c = cast[ptr Calc](ctx) |
| 156 | 157 | |
| 158 | + # Size the keys from the space actually on offer, so the keypad fills the | |
| 159 | + # window however the compositor decided to size it. Length::Fill does not | |
| 160 | + # widen a cosmic button, so the host does the arithmetic instead. | |
| 161 | + let | |
| 162 | + gap = space(SpaceXs) | |
| 163 | + pad = space(SpaceM) | |
| 164 | + display = 96.0 | |
| 165 | + keyW = max(MinKeyW, (float(width) - 2 * pad - float(Cols - 1) * gap) / float(Cols)) | |
| 166 | + keyH = max(MinKeyH, | |
| 167 | + (float(height) - display - 2 * pad - float(Rows - 1) * gap) / float(Rows)) | |
| 168 | + | |
| 157 | 169 | b.container: |
| 158 | 170 | b.fill() |
| 159 | 171 | b.alignCenter() |
| @@ -166,19 +178,17 @@ proc onView(ctx: pointer; b: Builder) {.cdecl.} = | ||
| 166 | 178 | b.text(c[].display, TextTitle1) |
| 167 | 179 | |
| 168 | 180 | b.column: |
| 169 | - b.fill() | |
| 170 | - b.spacing(space(SpaceXs)) | |
| 181 | + b.spacing(gap) | |
| 171 | 182 | for keyRow in Keypad: |
| 172 | 183 | b.row: |
| 173 | - b.spacing(space(SpaceXs)) | |
| 184 | + b.spacing(gap) | |
| 174 | 185 | b.alignCenter() |
| 175 | - b.fill() | |
| 176 | 186 | for key in keyRow: |
| 177 | - b.size(Fill, KeyH) | |
| 187 | + b.size(keyW, keyH) | |
| 178 | 188 | if key.label.len > 0: |
| 179 | 189 | b.button(key.label, key.id, key.style) |
| 180 | 190 | else: |
| 181 | - b.space(0, KeyH) # hold the column open | |
| 191 | + b.space(keyW, keyH) # hold the column open | |
| 182 | 192 | |
| 183 | 193 | proc main() = |
| 184 | 194 | var calc = newCalc() |
| @@ -24,9 +24,10 @@ type | |||
| 24 | error: bool | 24 | error: bool |
| 25 | 25 | ||
| 26 | const | 26 | const |
| 27 | - # Keys fill their row, so the four columns split the width evenly and grow | 27 | + Cols = 4 |
| 28 | - # with the window instead of each key hugging its own label. | 28 | + Rows = 5 |
| 29 | - KeyH = 84.0 | 29 | + MinKeyW = 56.0 |
| 30 | + MinKeyH = 44.0 | ||
| 30 | 31 | ||
| 31 | # 0..9 are the digit keys, so their id is their value. | 32 | # 0..9 are the digit keys, so their id is their value. |
| 32 | IdDot* = 10'i32 | 33 | IdDot* = 10'i32 |
| @@ -151,9 +152,20 @@ proc onPress*(ctx: pointer; id: int32) {.cdecl.} = | |||
| 151 | else: | 152 | else: |
| 152 | discard | 153 | discard |
| 153 | 154 | ||
| 154 | -proc onView(ctx: pointer; b: Builder) {.cdecl.} = | 155 | +proc onView(ctx: pointer; b: Builder; width, height: cfloat) {.cdecl.} = |
| 155 | let c = cast[ptr Calc](ctx) | 156 | let c = cast[ptr Calc](ctx) |
| 156 | 157 | ||
| 158 | + # Size the keys from the space actually on offer, so the keypad fills the | ||
| 159 | + # window however the compositor decided to size it. Length::Fill does not | ||
| 160 | + # widen a cosmic button, so the host does the arithmetic instead. | ||
| 161 | + let | ||
| 162 | + gap = space(SpaceXs) | ||
| 163 | + pad = space(SpaceM) | ||
| 164 | + display = 96.0 | ||
| 165 | + keyW = max(MinKeyW, (float(width) - 2 * pad - float(Cols - 1) * gap) / float(Cols)) | ||
| 166 | + keyH = max(MinKeyH, | ||
| 167 | + (float(height) - display - 2 * pad - float(Rows - 1) * gap) / float(Rows)) | ||
| 168 | + | ||
| 157 | b.container: | 169 | b.container: |
| 158 | b.fill() | 170 | b.fill() |
| 159 | b.alignCenter() | 171 | b.alignCenter() |
| @@ -166,19 +178,17 @@ proc onView(ctx: pointer; b: Builder) {.cdecl.} = | |||
| 166 | b.text(c[].display, TextTitle1) | 178 | b.text(c[].display, TextTitle1) |
| 167 | 179 | ||
| 168 | b.column: | 180 | b.column: |
| 169 | - b.fill() | 181 | + b.spacing(gap) |
| 170 | - b.spacing(space(SpaceXs)) | ||
| 171 | for keyRow in Keypad: | 182 | for keyRow in Keypad: |
| 172 | b.row: | 183 | b.row: |
| 173 | - b.spacing(space(SpaceXs)) | 184 | + b.spacing(gap) |
| 174 | b.alignCenter() | 185 | b.alignCenter() |
| 175 | - b.fill() | ||
| 176 | for key in keyRow: | 186 | for key in keyRow: |
| 177 | - b.size(Fill, KeyH) | 187 | + b.size(keyW, keyH) |
| 178 | if key.label.len > 0: | 188 | if key.label.len > 0: |
| 179 | b.button(key.label, key.id, key.style) | 189 | b.button(key.label, key.id, key.style) |
| 180 | else: | 190 | else: |
| 181 | - b.space(0, KeyH) # hold the column open | 191 | + b.space(keyW, keyH) # hold the column open |
| 182 | 192 | ||
| 183 | proc main() = | 193 | proc main() = |
| 184 | var calc = newCalc() | 194 | var calc = newCalc() |
modified
nim/cosmicnim.nim +3 -1 | @@ -31,7 +31,9 @@ type | ||
| 31 | 31 | Builder* = distinct pointer |
| 32 | 32 | ## Opaque. Only valid for the duration of the `onView` call it arrived in. |
| 33 | 33 | |
| 34 | - OnView* = proc (ctx: pointer; b: Builder) {.cdecl.} | |
| 34 | + OnView* = proc (ctx: pointer; b: Builder; width, height: cfloat) {.cdecl.} | |
| 35 | + ## `width`/`height` are the pixels actually available — not what | |
| 36 | + ## CosmicConfig asked for, which a tiling compositor will override. | |
| 35 | 37 | OnPress* = proc (ctx: pointer; id: int32) {.cdecl.} |
| 36 | 38 | |
| 37 | 39 | CosmicConfig* = object |
| @@ -31,7 +31,9 @@ type | |||
| 31 | Builder* = distinct pointer | 31 | Builder* = distinct pointer |
| 32 | ## Opaque. Only valid for the duration of the `onView` call it arrived in. | 32 | ## Opaque. Only valid for the duration of the `onView` call it arrived in. |
| 33 | 33 | ||
| 34 | - OnView* = proc (ctx: pointer; b: Builder) {.cdecl.} | 34 | + OnView* = proc (ctx: pointer; b: Builder; width, height: cfloat) {.cdecl.} |
| 35 | + ## `width`/`height` are the pixels actually available — not what | ||
| 36 | + ## CosmicConfig asked for, which a tiling compositor will override. | ||
| 35 | OnPress* = proc (ctx: pointer; id: int32) {.cdecl.} | 37 | OnPress* = proc (ctx: pointer; id: int32) {.cdecl.} |
| 36 | 38 | ||
| 37 | CosmicConfig* = object | 39 | CosmicConfig* = object |