1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
3
4//! Runtime support for layouts.
5
6// cspell:ignore coord
7
8use crate::items::{DialogButtonRole, LayoutAlignment};
9use crate::{slice::Slice, Coord, SharedVector};
10use alloc::vec::Vec;
11
12pub use crate::items::Orientation;
13
14/// The constraint that applies to an item
15// Also, the field needs to be in alphabetical order because how the generated code sort fields for struct
16#[repr(C)]
17#[derive(Clone, Copy, Debug, PartialEq)]
18pub struct LayoutInfo {
19 /// The maximum size for the item.
20 pub max: Coord,
21 /// The maximum size in percentage of the parent (value between 0 and 100).
22 pub max_percent: Coord,
23 /// The minimum size for this item.
24 pub min: Coord,
25 /// The minimum size in percentage of the parent (value between 0 and 100).
26 pub min_percent: Coord,
27 /// the preferred size
28 pub preferred: Coord,
29 /// the stretch factor
30 pub stretch: f32,
31}
32
33impl Default for LayoutInfo {
34 fn default() -> Self {
35 LayoutInfo {
36 min: 0 as _,
37 max: Coord::MAX,
38 min_percent: 0 as _,
39 max_percent: 100 as _,
40 preferred: 0 as _,
41 stretch: 0 as _,
42 }
43 }
44}
45
46impl LayoutInfo {
47 // Note: This "logic" is duplicated in the cpp generator's generated code for merging layout infos.
48 #[must_use]
49 pub fn merge(&self, other: &LayoutInfo) -> Self {
50 Self {
51 min: self.min.max(other.min),
52 max: self.max.min(other.max),
53 min_percent: self.min_percent.max(other.min_percent),
54 max_percent: self.max_percent.min(other.max_percent),
55 preferred: self.preferred.max(other.preferred),
56 stretch: self.stretch.min(other.stretch),
57 }
58 }
59
60 /// Helper function to return a preferred size which is within the min/max constraints
61 #[must_use]
62 pub fn preferred_bounded(&self) -> Coord {
63 self.preferred.min(self.max).max(self.min)
64 }
65}
66
67impl core::ops::Add for LayoutInfo {
68 type Output = Self;
69
70 fn add(self, rhs: Self) -> Self::Output {
71 self.merge(&rhs)
72 }
73}
74
75/// Returns the logical min and max sizes given the provided layout constraints.
76pub fn min_max_size_for_layout_constraints(
77 constraints_horizontal: LayoutInfo,
78 constraints_vertical: LayoutInfo,
79) -> (Option<crate::api::LogicalSize>, Option<crate::api::LogicalSize>) {
80 let min_width = constraints_horizontal.min.min(constraints_horizontal.max) as f32;
81 let min_height = constraints_vertical.min.min(constraints_vertical.max) as f32;
82 let max_width = constraints_horizontal.max.max(constraints_horizontal.min) as f32;
83 let max_height = constraints_vertical.max.max(constraints_vertical.min) as f32;
84
85 //cfg!(target_arch = "wasm32") is there because wasm32 winit don't like when max size is None:
86 // panicked at 'Property is read only: JsValue(NoModificationAllowedError: CSSStyleDeclaration.removeProperty: Can't remove property 'max-width' from computed style
87
88 let min_size = if min_width > 0. || min_height > 0. || cfg!(target_arch = "wasm32") {
89 Some(crate::api::LogicalSize::new(min_width, min_height))
90 } else {
91 None
92 };
93
94 let max_size = if (max_width > 0.
95 && max_height > 0.
96 && (max_width < i32::MAX as f32 || max_height < i32::MAX as f32))
97 || cfg!(target_arch = "wasm32")
98 {
99 // maximum widget size for Qt and a workaround for the winit api not allowing partial constraints
100 let window_size_max = 16_777_215.;
101 Some(crate::api::LogicalSize::new(
102 max_width.min(window_size_max),
103 max_height.min(window_size_max),
104 ))
105 } else {
106 None
107 };
108
109 (min_size, max_size)
110}
111
112/// Implement a saturating_add version for both possible value of Coord.
113/// So that adding the max value does not overflow
114trait Saturating {
115 fn add(_: Self, _: Self) -> Self;
116}
117impl Saturating for i32 {
118 #[inline]
119 fn add(a: Self, b: Self) -> Self {
120 a.saturating_add(b)
121 }
122}
123impl Saturating for f32 {
124 #[inline]
125 fn add(a: Self, b: Self) -> Self {
126 a + b
127 }
128}
129
130mod grid_internal {
131 use super::*;
132
133 fn order_coord<T: PartialOrd>(a: &T, b: &T) -> core::cmp::Ordering {
134 a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal)
135 }
136
137 #[derive(Debug, Clone)]
138 pub struct LayoutData {
139 // inputs
140 pub min: Coord,
141 pub max: Coord,
142 pub pref: Coord,
143 pub stretch: f32,
144
145 // outputs
146 pub pos: Coord,
147 pub size: Coord,
148 }
149
150 impl Default for LayoutData {
151 fn default() -> Self {
152 LayoutData {
153 min: 0 as _,
154 max: Coord::MAX,
155 pref: 0 as _,
156 stretch: f32::MAX,
157 pos: 0 as _,
158 size: 0 as _,
159 }
160 }
161 }
162
163 trait Adjust {
164 fn can_grow(_: &LayoutData) -> Coord;
165 fn to_distribute(expected_size: Coord, current_size: Coord) -> Coord;
166 fn distribute(_: &mut LayoutData, val: Coord);
167 }
168
169 struct Grow;
170 impl Adjust for Grow {
171 fn can_grow(it: &LayoutData) -> Coord {
172 it.max - it.size
173 }
174
175 fn to_distribute(expected_size: Coord, current_size: Coord) -> Coord {
176 expected_size - current_size
177 }
178
179 fn distribute(it: &mut LayoutData, val: Coord) {
180 it.size += val;
181 }
182 }
183
184 struct Shrink;
185 impl Adjust for Shrink {
186 fn can_grow(it: &LayoutData) -> Coord {
187 it.size - it.min
188 }
189
190 fn to_distribute(expected_size: Coord, current_size: Coord) -> Coord {
191 current_size - expected_size
192 }
193
194 fn distribute(it: &mut LayoutData, val: Coord) {
195 it.size -= val;
196 }
197 }
198
199 #[allow(clippy::unnecessary_cast)] // Coord
200 fn adjust_items<A: Adjust>(data: &mut [LayoutData], size_without_spacing: Coord) -> Option<()> {
201 loop {
202 let size_cannot_grow: Coord = data
203 .iter()
204 .filter(|it| A::can_grow(it) <= 0 as _)
205 .map(|it| it.size)
206 .fold(0 as Coord, Saturating::add);
207
208 let total_stretch: f32 =
209 data.iter().filter(|it| A::can_grow(it) > 0 as _).map(|it| it.stretch).sum();
210
211 let actual_stretch = |s: f32| if total_stretch <= 0. { 1. } else { s };
212
213 let max_grow = data
214 .iter()
215 .filter(|it| A::can_grow(it) > 0 as _)
216 .map(|it| A::can_grow(it) as f32 / actual_stretch(it.stretch))
217 .min_by(order_coord)?;
218
219 let current_size: Coord = data
220 .iter()
221 .filter(|it| A::can_grow(it) > 0 as _)
222 .map(|it| it.size)
223 .fold(0 as _, Saturating::add);
224
225 //let to_distribute = size_without_spacing - (size_cannot_grow + current_size);
226 let to_distribute =
227 A::to_distribute(size_without_spacing, size_cannot_grow + current_size) as f32;
228 if to_distribute <= 0. || max_grow <= 0. {
229 return Some(());
230 }
231
232 let grow = if total_stretch <= 0. {
233 to_distribute
234 / (data.iter().filter(|it| A::can_grow(it) > 0 as _).count() as Coord) as f32
235 } else {
236 to_distribute / total_stretch
237 }
238 .min(max_grow);
239
240 let mut distributed = 0 as Coord;
241 for it in data.iter_mut().filter(|it| A::can_grow(it) > 0 as Coord) {
242 let val = (grow * actual_stretch(it.stretch)) as Coord;
243 A::distribute(it, val);
244 distributed += val;
245 }
246
247 if distributed <= 0 as Coord {
248 // This can happen when Coord is integer and there is less then a pixel to add to each elements
249 // just give the pixel to the one with the bigger stretch
250 if let Some(it) = data
251 .iter_mut()
252 .filter(|it| A::can_grow(it) > 0 as _)
253 .max_by(|a, b| actual_stretch(a.stretch).total_cmp(&b.stretch))
254 {
255 A::distribute(it, to_distribute as Coord);
256 }
257 return Some(());
258 }
259 }
260 }
261
262 pub fn layout_items(data: &mut [LayoutData], start_pos: Coord, size: Coord, spacing: Coord) {
263 let size_without_spacing = size - spacing * (data.len() - 1) as Coord;
264
265 let mut pref = 0 as Coord;
266 for it in data.iter_mut() {
267 it.size = it.pref;
268 pref += it.pref;
269 }
270 if size_without_spacing >= pref {
271 adjust_items::<Grow>(data, size_without_spacing);
272 } else if size_without_spacing < pref {
273 adjust_items::<Shrink>(data, size_without_spacing);
274 }
275
276 let mut pos = start_pos;
277 for it in data.iter_mut() {
278 it.pos = pos;
279 pos = Saturating::add(pos, Saturating::add(it.size, spacing));
280 }
281 }
282
283 #[test]
284 #[allow(clippy::float_cmp)] // We want bit-wise equality here
285 fn test_layout_items() {
286 let my_items = &mut [
287 LayoutData { min: 100., max: 200., pref: 100., stretch: 1., ..Default::default() },
288 LayoutData { min: 50., max: 300., pref: 100., stretch: 1., ..Default::default() },
289 LayoutData { min: 50., max: 150., pref: 100., stretch: 1., ..Default::default() },
290 ];
291
292 layout_items(my_items, 100., 650., 0.);
293 assert_eq!(my_items[0].size, 200.);
294 assert_eq!(my_items[1].size, 300.);
295 assert_eq!(my_items[2].size, 150.);
296
297 layout_items(my_items, 100., 200., 0.);
298 assert_eq!(my_items[0].size, 100.);
299 assert_eq!(my_items[1].size, 50.);
300 assert_eq!(my_items[2].size, 50.);
301
302 layout_items(my_items, 100., 300., 0.);
303 assert_eq!(my_items[0].size, 100.);
304 assert_eq!(my_items[1].size, 100.);
305 assert_eq!(my_items[2].size, 100.);
306 }
307
308 /// Create a vector of LayoutData for an array of GridLayoutCellData
309 pub fn to_layout_data(
310 data: &[GridLayoutCellData],
311 spacing: Coord,
312 size: Option<Coord>,
313 ) -> Vec<LayoutData> {
314 let mut num = 0;
315 for cell in data {
316 num = num.max(cell.col_or_row + cell.span);
317 }
318 if num < 1 {
319 return Default::default();
320 }
321 let mut layout_data = alloc::vec![grid_internal::LayoutData { stretch: 1., ..Default::default() }; num as usize];
322 let mut has_spans = false;
323 for cell in data {
324 let constraint = &cell.constraint;
325 let mut max = constraint.max;
326 if let Some(size) = size {
327 max = max.min(size * constraint.max_percent / 100 as Coord);
328 }
329 for c in 0..(cell.span as usize) {
330 let cdata = &mut layout_data[cell.col_or_row as usize + c];
331 cdata.max = cdata.max.min(max);
332 }
333 if cell.span == 1 {
334 let mut min = constraint.min;
335 if let Some(size) = size {
336 min = min.max(size * constraint.min_percent / 100 as Coord);
337 }
338 let pref = constraint.preferred.min(max).max(min);
339 let cdata = &mut layout_data[cell.col_or_row as usize];
340 cdata.min = cdata.min.max(min);
341 cdata.pref = cdata.pref.max(pref);
342 cdata.stretch = cdata.stretch.min(constraint.stretch);
343 } else {
344 has_spans = true;
345 }
346 }
347 if has_spans {
348 // Adjust minimum sizes
349 for cell in data.iter().filter(|cell| cell.span > 1) {
350 let span_data = &mut layout_data
351 [(cell.col_or_row as usize)..(cell.col_or_row + cell.span) as usize];
352 let mut min = cell.constraint.min;
353 if let Some(size) = size {
354 min = min.max(size * cell.constraint.min_percent / 100 as Coord);
355 }
356 grid_internal::layout_items(span_data, 0 as _, min, spacing);
357 for cdata in span_data {
358 if cdata.min < cdata.size {
359 cdata.min = cdata.size;
360 }
361 }
362 }
363 // Adjust maximum sizes
364 for cell in data.iter().filter(|cell| cell.span > 1) {
365 let span_data = &mut layout_data
366 [(cell.col_or_row as usize)..(cell.col_or_row + cell.span) as usize];
367 let mut max = cell.constraint.max;
368 if let Some(size) = size {
369 max = max.min(size * cell.constraint.max_percent / 100 as Coord);
370 }
371 grid_internal::layout_items(span_data, 0 as _, max, spacing);
372 for cdata in span_data {
373 if cdata.max > cdata.size {
374 cdata.max = cdata.size;
375 }
376 }
377 }
378 // Adjust preferred sizes
379 for cell in data.iter().filter(|cell| cell.span > 1) {
380 let span_data = &mut layout_data
381 [(cell.col_or_row as usize)..(cell.col_or_row + cell.span) as usize];
382 grid_internal::layout_items(span_data, 0 as _, cell.constraint.preferred, spacing);
383 for cdata in span_data {
384 cdata.pref = cdata.pref.max(cdata.size).min(cdata.max).max(cdata.min);
385 }
386 }
387 // Adjust stretches
388 for cell in data.iter().filter(|cell| cell.span > 1) {
389 let span_data = &mut layout_data
390 [(cell.col_or_row as usize)..(cell.col_or_row + cell.span) as usize];
391 let total_stretch: f32 = span_data.iter().map(|c| c.stretch).sum();
392 if total_stretch > cell.constraint.stretch {
393 for cdata in span_data {
394 cdata.stretch *= cell.constraint.stretch / total_stretch;
395 }
396 }
397 }
398 }
399 layout_data
400 }
401}
402
403#[repr(C)]
404pub struct Constraint {
405 pub min: Coord,
406 pub max: Coord,
407}
408
409impl Default for Constraint {
410 fn default() -> Self {
411 Constraint { min: 0 as Coord, max: Coord::MAX }
412 }
413}
414
415#[repr(C)]
416#[derive(Copy, Clone, Debug, Default)]
417pub struct Padding {
418 pub begin: Coord,
419 pub end: Coord,
420}
421
422#[repr(C)]
423#[derive(Debug)]
424pub struct GridLayoutData<'a> {
425 pub size: Coord,
426 pub spacing: Coord,
427 pub padding: Padding,
428 pub cells: Slice<'a, GridLayoutCellData>,
429}
430
431#[repr(C)]
432#[derive(Default, Debug)]
433pub struct GridLayoutCellData {
434 /// col, or row.
435 pub col_or_row: u16,
436 /// colspan or rowspan
437 pub span: u16,
438 pub constraint: LayoutInfo,
439}
440
441/// return, an array which is of size `data.cells.len() * 2` which for each cell we give the pos, size
442pub fn solve_grid_layout(data: &GridLayoutData) -> SharedVector<Coord> {
443 let mut layout_data =
444 grid_internal::to_layout_data(data.cells.as_slice(), data.spacing, Some(data.size));
445
446 if layout_data.is_empty() {
447 return Default::default();
448 }
449
450 grid_internal::layout_items(
451 &mut layout_data,
452 data.padding.begin,
453 data.size - (data.padding.begin + data.padding.end),
454 data.spacing,
455 );
456
457 let mut result = SharedVector::with_capacity(4 * data.cells.len());
458 for cell in data.cells.iter() {
459 let cdata = &layout_data[cell.col_or_row as usize];
460 result.push(cdata.pos);
461 result.push({
462 let first_cell = &layout_data[cell.col_or_row as usize];
463 let last_cell = &layout_data[cell.col_or_row as usize + cell.span as usize - 1];
464 last_cell.pos + last_cell.size - first_cell.pos
465 });
466 }
467 result
468}
469
470pub fn grid_layout_info(
471 cells: Slice<GridLayoutCellData>,
472 spacing: Coord,
473 padding: &Padding,
474) -> LayoutInfo {
475 let layout_data: Vec = grid_internal::to_layout_data(data:cells.as_slice(), spacing, size:None);
476 if layout_data.is_empty() {
477 return Default::default();
478 }
479 let spacing_w: f32 = spacing * (layout_data.len() - 1) as Coord + padding.begin + padding.end;
480 let min: f32 = layout_data.iter().map(|data: &LayoutData| data.min).sum::<Coord>() + spacing_w;
481 let max: f32 = layout_data.iter().map(|data| data.max).fold(init:spacing_w, f:Saturating::add);
482 let preferred: f32 = layout_data.iter().map(|data: &LayoutData| data.pref).sum::<Coord>() + spacing_w;
483 let stretch: f32 = layout_data.iter().map(|data: &LayoutData| data.stretch).sum::<f32>();
484 LayoutInfo { min, max, min_percent: 0 as _, max_percent: 100 as _, preferred, stretch }
485}
486
487#[repr(C)]
488#[derive(Debug)]
489/// The BoxLayoutData is used to represent both a Horizontal and Vertical layout.
490/// The width/height x/y correspond to that of a horizontal layout.
491/// For vertical layout, they are inverted
492pub struct BoxLayoutData<'a> {
493 pub size: Coord,
494 pub spacing: Coord,
495 pub padding: Padding,
496 pub alignment: LayoutAlignment,
497 pub cells: Slice<'a, BoxLayoutCellData>,
498}
499
500#[repr(C)]
501#[derive(Default, Debug, Clone)]
502pub struct BoxLayoutCellData {
503 pub constraint: LayoutInfo,
504}
505
506/// Solve a BoxLayout
507pub fn solve_box_layout(data: &BoxLayoutData, repeater_indexes: Slice<u32>) -> SharedVector<Coord> {
508 let mut result = SharedVector::<Coord>::default();
509 result.resize(data.cells.len() * 2 + repeater_indexes.len(), 0 as _);
510
511 if data.cells.is_empty() {
512 return result;
513 }
514
515 let mut layout_data: Vec<_> = data
516 .cells
517 .iter()
518 .map(|c| {
519 let min = c.constraint.min.max(c.constraint.min_percent * data.size / 100 as Coord);
520 let max = c.constraint.max.min(c.constraint.max_percent * data.size / 100 as Coord);
521 grid_internal::LayoutData {
522 min,
523 max,
524 pref: c.constraint.preferred.min(max).max(min),
525 stretch: c.constraint.stretch,
526 ..Default::default()
527 }
528 })
529 .collect();
530
531 let size_without_padding = data.size - data.padding.begin - data.padding.end;
532 let pref_size: Coord = layout_data.iter().map(|it| it.pref).sum();
533 let num_spacings = (layout_data.len() - 1) as Coord;
534 let spacings = data.spacing * num_spacings;
535
536 let align = match data.alignment {
537 LayoutAlignment::Stretch => {
538 grid_internal::layout_items(
539 &mut layout_data,
540 data.padding.begin,
541 size_without_padding,
542 data.spacing,
543 );
544 None
545 }
546 _ if size_without_padding <= pref_size + spacings => {
547 grid_internal::layout_items(
548 &mut layout_data,
549 data.padding.begin,
550 size_without_padding,
551 data.spacing,
552 );
553 None
554 }
555 LayoutAlignment::Center => Some((
556 data.padding.begin + (size_without_padding - pref_size - spacings) / 2 as Coord,
557 data.spacing,
558 )),
559 LayoutAlignment::Start => Some((data.padding.begin, data.spacing)),
560 LayoutAlignment::End => {
561 Some((data.padding.begin + (size_without_padding - pref_size - spacings), data.spacing))
562 }
563 LayoutAlignment::SpaceBetween => {
564 Some((data.padding.begin, (size_without_padding - pref_size) / num_spacings))
565 }
566 LayoutAlignment::SpaceAround => {
567 let spacing = (size_without_padding - pref_size) / (num_spacings + 1 as Coord);
568 Some((data.padding.begin + spacing / 2 as Coord, spacing))
569 }
570 };
571 if let Some((mut pos, spacing)) = align {
572 for it in &mut layout_data {
573 it.pos = pos;
574 it.size = it.pref;
575 pos += spacing + it.size;
576 }
577 }
578
579 let res = result.make_mut_slice();
580
581 // The index/2 in result in which we should add the next repeated item
582 let mut repeat_offset =
583 res.len() / 2 - repeater_indexes.iter().skip(1).step_by(2).sum::<u32>() as usize;
584 // The index/2 in repeater_indexes
585 let mut next_rep = 0;
586 // The index/2 in result in which we should add the next non-repeated item
587 let mut current_offset = 0;
588 for (idx, layout) in layout_data.iter().enumerate() {
589 let o = loop {
590 if let Some(nr) = repeater_indexes.get(next_rep * 2) {
591 let nr = *nr as usize;
592 if nr == idx {
593 for o in 0..2 {
594 res[current_offset * 2 + o] = (repeat_offset * 2 + o) as _;
595 }
596 current_offset += 1;
597 }
598 if idx >= nr {
599 if idx - nr == repeater_indexes[next_rep * 2 + 1] as usize {
600 next_rep += 1;
601 continue;
602 }
603 repeat_offset += 1;
604 break repeat_offset - 1;
605 }
606 }
607 current_offset += 1;
608 break current_offset - 1;
609 };
610 res[o * 2] = layout.pos;
611 res[o * 2 + 1] = layout.size;
612 }
613 result
614}
615
616/// Return the LayoutInfo for a BoxLayout with the given cells.
617pub fn box_layout_info(
618 cells: Slice<BoxLayoutCellData>,
619 spacing: Coord,
620 padding: &Padding,
621 alignment: LayoutAlignment,
622) -> LayoutInfo {
623 let count: usize = cells.len();
624 if count < 1 {
625 return LayoutInfo { max: 0 as _, ..LayoutInfo::default() };
626 };
627 let is_stretch: bool = alignment == LayoutAlignment::Stretch;
628 let extra_w: f32 = padding.begin + padding.end + spacing * (count - 1) as Coord;
629 let min: f32 = cells.iter().map(|c: &BoxLayoutCellData| c.constraint.min).sum::<Coord>() + extra_w;
630 let max: f32 = if is_stretch {
631 (cells.iter().map(|c| c.constraint.max).fold(init:extra_w, f:Saturating::add)).max(min)
632 } else {
633 Coord::MAX
634 };
635 let preferred: f32 = cells.iter().map(|c: &BoxLayoutCellData| c.constraint.preferred_bounded()).sum::<Coord>() + extra_w;
636 let stretch: f32 = cells.iter().map(|c: &BoxLayoutCellData| c.constraint.stretch).sum::<f32>();
637 LayoutInfo { min, max, min_percent: 0 as _, max_percent: 100 as _, preferred, stretch }
638}
639
640pub fn box_layout_info_ortho(cells: Slice<BoxLayoutCellData>, padding: &Padding) -> LayoutInfo {
641 let count: usize = cells.len();
642 if count < 1 {
643 return LayoutInfo { max: 0 as _, ..LayoutInfo::default() };
644 };
645 let extra_w: f32 = padding.begin + padding.end;
646
647 let mut fold: LayoutInfo =
648 cells.iter().fold(init:LayoutInfo { stretch: f32::MAX, ..Default::default() }, |a: LayoutInfo, b: &BoxLayoutCellData| {
649 a.merge(&b.constraint)
650 });
651 fold.max = fold.max.max(fold.min);
652 fold.preferred = fold.preferred.clamp(fold.min, fold.max);
653 fold.min += extra_w;
654 fold.max = Saturating::add(fold.max, extra_w);
655 fold.preferred += extra_w;
656 fold
657}
658
659/// Given the cells of a layout of a Dialog, re-order the button according to the platform
660///
661/// This function assume that the `roles` contains the roles of the button which are the first `cells`
662/// It will simply change the column field of the cell
663pub fn reorder_dialog_button_layout(cells: &mut [GridLayoutCellData], roles: &[DialogButtonRole]) {
664 fn add_buttons(
665 cells: &mut [GridLayoutCellData],
666 roles: &[DialogButtonRole],
667 idx: &mut u16,
668 role: DialogButtonRole,
669 ) {
670 for (cell, r) in cells.iter_mut().zip(roles.iter()) {
671 if *r == role {
672 cell.col_or_row = *idx;
673 *idx += 1;
674 }
675 }
676 }
677
678 #[cfg(feature = "std")]
679 fn is_kde() -> bool {
680 // assume some unix check if XDG_CURRENT_DESKTOP stats with K
681 std::env::var("XDG_CURRENT_DESKTOP")
682 .ok()
683 .and_then(|v| v.as_bytes().first().copied())
684 .map_or(false, |x| x.to_ascii_uppercase() == b'K')
685 }
686 #[cfg(not(feature = "std"))]
687 let is_kde = || true;
688
689 let mut idx = 0;
690
691 if cfg!(windows) {
692 add_buttons(cells, roles, &mut idx, DialogButtonRole::Reset);
693 idx += 1;
694 add_buttons(cells, roles, &mut idx, DialogButtonRole::Accept);
695 add_buttons(cells, roles, &mut idx, DialogButtonRole::Action);
696 add_buttons(cells, roles, &mut idx, DialogButtonRole::Reject);
697 add_buttons(cells, roles, &mut idx, DialogButtonRole::Apply);
698 add_buttons(cells, roles, &mut idx, DialogButtonRole::Help);
699 } else if cfg!(target_os = "macos") {
700 add_buttons(cells, roles, &mut idx, DialogButtonRole::Help);
701 add_buttons(cells, roles, &mut idx, DialogButtonRole::Reset);
702 add_buttons(cells, roles, &mut idx, DialogButtonRole::Apply);
703 add_buttons(cells, roles, &mut idx, DialogButtonRole::Action);
704 idx += 1;
705 add_buttons(cells, roles, &mut idx, DialogButtonRole::Reject);
706 add_buttons(cells, roles, &mut idx, DialogButtonRole::Accept);
707 } else if is_kde() {
708 // KDE variant
709 add_buttons(cells, roles, &mut idx, DialogButtonRole::Help);
710 add_buttons(cells, roles, &mut idx, DialogButtonRole::Reset);
711 idx += 1;
712 add_buttons(cells, roles, &mut idx, DialogButtonRole::Action);
713 add_buttons(cells, roles, &mut idx, DialogButtonRole::Accept);
714 add_buttons(cells, roles, &mut idx, DialogButtonRole::Apply);
715 add_buttons(cells, roles, &mut idx, DialogButtonRole::Reject);
716 } else {
717 // GNOME variant and fallback for WASM build
718 add_buttons(cells, roles, &mut idx, DialogButtonRole::Help);
719 add_buttons(cells, roles, &mut idx, DialogButtonRole::Reset);
720 idx += 1;
721 add_buttons(cells, roles, &mut idx, DialogButtonRole::Action);
722 add_buttons(cells, roles, &mut idx, DialogButtonRole::Apply);
723 add_buttons(cells, roles, &mut idx, DialogButtonRole::Reject);
724 add_buttons(cells, roles, &mut idx, DialogButtonRole::Accept);
725 }
726}
727
728#[cfg(feature = "ffi")]
729pub(crate) mod ffi {
730 #![allow(unsafe_code)]
731
732 use super::*;
733
734 #[no_mangle]
735 pub extern "C" fn slint_solve_grid_layout(
736 data: &GridLayoutData,
737 result: &mut SharedVector<Coord>,
738 ) {
739 *result = super::solve_grid_layout(data)
740 }
741
742 #[no_mangle]
743 pub extern "C" fn slint_grid_layout_info(
744 cells: Slice<GridLayoutCellData>,
745 spacing: Coord,
746 padding: &Padding,
747 ) -> LayoutInfo {
748 super::grid_layout_info(cells, spacing, padding)
749 }
750
751 #[no_mangle]
752 pub extern "C" fn slint_solve_box_layout(
753 data: &BoxLayoutData,
754 repeater_indexes: Slice<u32>,
755 result: &mut SharedVector<Coord>,
756 ) {
757 *result = super::solve_box_layout(data, repeater_indexes)
758 }
759
760 #[no_mangle]
761 /// Return the LayoutInfo for a BoxLayout with the given cells.
762 pub extern "C" fn slint_box_layout_info(
763 cells: Slice<BoxLayoutCellData>,
764 spacing: Coord,
765 padding: &Padding,
766 alignment: LayoutAlignment,
767 ) -> LayoutInfo {
768 super::box_layout_info(cells, spacing, padding, alignment)
769 }
770
771 #[no_mangle]
772 /// Return the LayoutInfo for a BoxLayout with the given cells.
773 pub extern "C" fn slint_box_layout_info_ortho(
774 cells: Slice<BoxLayoutCellData>,
775 padding: &Padding,
776 ) -> LayoutInfo {
777 super::box_layout_info_ortho(cells, padding)
778 }
779
780 /// Calls [`reorder_dialog_button_layout`].
781 ///
782 /// Safety: `cells` must be a pointer to a mutable array of cell data, the array must have at
783 /// least `roles.len()` elements.
784 #[no_mangle]
785 pub unsafe extern "C" fn slint_reorder_dialog_button_layout(
786 cells: *mut GridLayoutCellData,
787 roles: Slice<DialogButtonRole>,
788 ) {
789 reorder_dialog_button_layout(core::slice::from_raw_parts_mut(cells, roles.len()), &roles);
790 }
791}
792