forked from 1b-agent/opencadstudio-upstream
perf(ribbon): build only selected density after measuring
This commit is contained in:
1 parent
140303217f
commit
f0479a5fc6
6 files changed
+260
-63
No files matched your search
@@ -215,6 +215,10 @@ strip = true
|
|||||||
name = "performance_benchmarks"
|
name = "performance_benchmarks"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "ribbon_layout_benchmark"
|
||||||
|
harness = false
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "command_benchmarks"
|
name = "command_benchmarks"
|
||||||
harness = false
|
harness = false
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
//! End-to-end ribbon view construction and layout benchmark.
|
||||||
|
//!
|
||||||
|
//! Unlike the broader performance bench's widget-construction-only metric, this
|
||||||
|
//! carries Iced's widget-tree cache across frames and runs layout at a fixed size.
|
||||||
|
|
||||||
|
use std::hint::black_box;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use OpenCADStudio::ui::ribbon::{LayerInfo, Ribbon};
|
||||||
|
use iced::{Color, Size};
|
||||||
|
use iced_test::runtime::user_interface::{Cache, UserInterface};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut ribbon = Ribbon::new();
|
||||||
|
ribbon.set_styles(
|
||||||
|
vec!["Standard".into(), "Title".into(), "Annotative".into()],
|
||||||
|
"Standard",
|
||||||
|
vec!["Standard".into()],
|
||||||
|
"Standard",
|
||||||
|
vec!["Standard".into()],
|
||||||
|
"Standard",
|
||||||
|
vec!["Standard".into()],
|
||||||
|
"Standard",
|
||||||
|
);
|
||||||
|
ribbon.set_layers(
|
||||||
|
["0", "DRAWING", "DIMENSIONS", "ANNOTATIONS"]
|
||||||
|
.into_iter()
|
||||||
|
.map(|name| LayerInfo {
|
||||||
|
name: name.into(),
|
||||||
|
color: Color::TRANSPARENT,
|
||||||
|
visible: true,
|
||||||
|
frozen: false,
|
||||||
|
locked: false,
|
||||||
|
vp_frozen: None,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
"0",
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut renderer = iced_test::futures::futures::executor::block_on(
|
||||||
|
<iced::Renderer as iced_test::core::renderer::Headless>::new(
|
||||||
|
iced_test::core::renderer::Settings::default(),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.expect("headless renderer");
|
||||||
|
let mut cache = Cache::default();
|
||||||
|
let bounds = Size::new(1600.0, 180.0);
|
||||||
|
|
||||||
|
let frame = |cache, renderer: &mut iced::Renderer| {
|
||||||
|
let ui = UserInterface::build(
|
||||||
|
ribbon.view(false, false, 0, 0, false, None),
|
||||||
|
bounds,
|
||||||
|
cache,
|
||||||
|
renderer,
|
||||||
|
);
|
||||||
|
black_box(ui.into_cache())
|
||||||
|
};
|
||||||
|
|
||||||
|
for _ in 0..10 {
|
||||||
|
cache = frame(cache, &mut renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
let iterations = 100;
|
||||||
|
let runs = 7;
|
||||||
|
let mut samples = Vec::with_capacity(runs);
|
||||||
|
for _ in 0..runs {
|
||||||
|
let start = Instant::now();
|
||||||
|
for _ in 0..iterations {
|
||||||
|
cache = frame(cache, &mut renderer);
|
||||||
|
}
|
||||||
|
samples.push(start.elapsed().as_secs_f64() * 1_000_000.0 / iterations as f64);
|
||||||
|
}
|
||||||
|
samples.sort_by(f64::total_cmp);
|
||||||
|
println!(
|
||||||
|
"ribbon view + Iced layout (cached steady state): median {:.1} µs/frame; samples {:?}",
|
||||||
|
samples[runs / 2],
|
||||||
|
samples
|
||||||
|
);
|
||||||
|
}
|
||||||
+141
-10
@@ -24,10 +24,25 @@ use crate::app::Message;
|
|||||||
/// One panel in its four density renderings. `tight` is the collapsed button
|
/// One panel in its four density renderings. `tight` is the collapsed button
|
||||||
/// with a small representative icon instead of the large one — the last step
|
/// with a small representative icon instead of the large one — the last step
|
||||||
/// before the buttons are squeezed together. An open flyout draws the panel's
|
/// before the buttons are squeezed together. An open flyout draws the panel's
|
||||||
/// own `full` rendering; see `FlyoutOverlay`. `elements` is indexed by `Level`.
|
/// own `full` rendering; see `FlyoutOverlay`. Elements are built lazily and
|
||||||
|
/// indexed by `Level`.
|
||||||
pub(crate) struct Panel<'a> {
|
pub(crate) struct Panel<'a> {
|
||||||
pub(crate) id: String,
|
pub(crate) id: String,
|
||||||
pub(crate) elements: [Element<'a, Message>; 4],
|
elements: [Option<Element<'a, Message>>; SLOTS],
|
||||||
|
build: Box<dyn Fn(Level) -> Element<'a, Message> + 'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Panel<'a> {
|
||||||
|
pub(crate) fn deferred(
|
||||||
|
id: impl Into<String>,
|
||||||
|
build: impl Fn(Level) -> Element<'a, Message> + 'a,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
id: id.into(),
|
||||||
|
elements: std::array::from_fn(|_| None),
|
||||||
|
build: Box::new(build),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Number of tree slots per panel: [full, compact, button, tight].
|
// Number of tree slots per panel: [full, compact, button, tight].
|
||||||
@@ -236,11 +251,24 @@ impl<'a> CollapsePanels<'a> {
|
|||||||
/// The shown element of panel `i` at `level` — an index into its `elements`
|
/// The shown element of panel `i` at `level` — an index into its `elements`
|
||||||
/// array, which the tree slots mirror (`slot(i, level)`).
|
/// array, which the tree slots mirror (`slot(i, level)`).
|
||||||
fn shown(&self, i: usize, level: Level) -> &Element<'a, Message> {
|
fn shown(&self, i: usize, level: Level) -> &Element<'a, Message> {
|
||||||
&self.panels[i].elements[level as usize]
|
self.panels[i].elements[level as usize]
|
||||||
|
.as_ref()
|
||||||
|
.expect("selected ribbon density is built during layout")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shown_mut(&mut self, i: usize, level: Level) -> &mut Element<'a, Message> {
|
fn shown_mut(&mut self, i: usize, level: Level) -> &mut Element<'a, Message> {
|
||||||
&mut self.panels[i].elements[level as usize]
|
self.panels[i].elements[level as usize]
|
||||||
|
.as_mut()
|
||||||
|
.expect("selected ribbon density is built during layout")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_element(&mut self, tree: &mut widget::Tree, i: usize, level: Level) {
|
||||||
|
let index = level as usize;
|
||||||
|
if self.panels[i].elements[index].is_none() {
|
||||||
|
let mut element = (self.panels[i].build)(level);
|
||||||
|
tree.children[slot(i, level)].diff(element.as_widget_mut());
|
||||||
|
self.panels[i].elements[index] = Some(element);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn levels_snapshot(&self, n: usize) -> Vec<Level> {
|
fn levels_snapshot(&self, n: usize) -> Vec<Level> {
|
||||||
@@ -260,11 +288,16 @@ impl<'a> Widget<Message, Theme, Renderer> for CollapsePanels<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn diff(&mut self, tree: &mut widget::Tree) {
|
fn diff(&mut self, tree: &mut widget::Tree) {
|
||||||
let mut refs = Vec::with_capacity(self.panels.len() * SLOTS);
|
let child_count = self.panels.len() * SLOTS;
|
||||||
for p in &mut self.panels {
|
tree.children.truncate(child_count);
|
||||||
refs.extend(p.elements.iter_mut());
|
tree.children.resize_with(child_count, widget::Tree::empty);
|
||||||
|
for (panel_index, panel) in self.panels.iter_mut().enumerate() {
|
||||||
|
for level in [Level::Full, Level::Compact, Level::Collapsed, Level::Tight] {
|
||||||
|
if let Some(element) = panel.elements[level as usize].as_mut() {
|
||||||
|
tree.children[slot(panel_index, level)].diff(element.as_widget_mut());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
tree.diff_children(&mut refs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size(&self) -> Size<Length> {
|
fn size(&self) -> Size<Length> {
|
||||||
@@ -294,11 +327,17 @@ impl<'a> Widget<Message, Theme, Renderer> for CollapsePanels<'a> {
|
|||||||
heights.clone_from(&cache.heights);
|
heights.clone_from(&cache.heights);
|
||||||
} else {
|
} else {
|
||||||
for i in 0..n {
|
for i in 0..n {
|
||||||
|
self.ensure_element(tree, i, Level::Full);
|
||||||
let f = self.panels[i].elements[Level::Full as usize]
|
let f = self.panels[i].elements[Level::Full as usize]
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
.as_widget_mut()
|
.as_widget_mut()
|
||||||
.layout(&mut tree.children[slot(i, Level::Full)], renderer, &natural)
|
.layout(&mut tree.children[slot(i, Level::Full)], renderer, &natural)
|
||||||
.size();
|
.size();
|
||||||
|
self.ensure_element(tree, i, Level::Compact);
|
||||||
let c = self.panels[i].elements[Level::Compact as usize]
|
let c = self.panels[i].elements[Level::Compact as usize]
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
.as_widget_mut()
|
.as_widget_mut()
|
||||||
.layout(
|
.layout(
|
||||||
&mut tree.children[slot(i, Level::Compact)],
|
&mut tree.children[slot(i, Level::Compact)],
|
||||||
@@ -306,7 +345,10 @@ impl<'a> Widget<Message, Theme, Renderer> for CollapsePanels<'a> {
|
|||||||
&natural,
|
&natural,
|
||||||
)
|
)
|
||||||
.size();
|
.size();
|
||||||
|
self.ensure_element(tree, i, Level::Collapsed);
|
||||||
let b = self.panels[i].elements[Level::Collapsed as usize]
|
let b = self.panels[i].elements[Level::Collapsed as usize]
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
.as_widget_mut()
|
.as_widget_mut()
|
||||||
.layout(
|
.layout(
|
||||||
&mut tree.children[slot(i, Level::Collapsed)],
|
&mut tree.children[slot(i, Level::Collapsed)],
|
||||||
@@ -314,7 +356,10 @@ impl<'a> Widget<Message, Theme, Renderer> for CollapsePanels<'a> {
|
|||||||
&natural,
|
&natural,
|
||||||
)
|
)
|
||||||
.size();
|
.size();
|
||||||
|
self.ensure_element(tree, i, Level::Tight);
|
||||||
let t = self.panels[i].elements[Level::Tight as usize]
|
let t = self.panels[i].elements[Level::Tight as usize]
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
.as_widget_mut()
|
.as_widget_mut()
|
||||||
.layout(
|
.layout(
|
||||||
&mut tree.children[slot(i, Level::Tight)],
|
&mut tree.children[slot(i, Level::Tight)],
|
||||||
@@ -342,6 +387,18 @@ impl<'a> Widget<Message, Theme, Renderer> for CollapsePanels<'a> {
|
|||||||
decide_levels(self.mode, &widths, max_w)
|
decide_levels(self.mode, &widths, max_w)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Measurements let Auto choose a density without building all variants
|
||||||
|
// again. On a cache hit only the selected element is built. If a
|
||||||
|
// collapsed panel is open, its Full element is also needed by the flyout.
|
||||||
|
for (i, &level) in levels.iter().enumerate() {
|
||||||
|
self.ensure_element(tree, i, level);
|
||||||
|
if matches!(level, Level::Collapsed | Level::Tight)
|
||||||
|
&& self.open.as_deref() == Some(self.panels[i].id.as_str())
|
||||||
|
{
|
||||||
|
self.ensure_element(tree, i, Level::Full);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The row is "tight" once any panel has dropped to its small icon — the
|
// The row is "tight" once any panel has dropped to its small icon — the
|
||||||
// last, most cramped state. The tab bar hides its mode selector then.
|
// last, most cramped state. The tab bar hides its mode selector then.
|
||||||
if let Some(out) = &self.tight_out {
|
if let Some(out) = &self.tight_out {
|
||||||
@@ -572,7 +629,9 @@ impl<'a> Widget<Message, Theme, Renderer> for CollapsePanels<'a> {
|
|||||||
// never shows slot 0 while the panel is button-form, so it
|
// never shows slot 0 while the panel is button-form, so it
|
||||||
// is free to double as the overlay content).
|
// is free to double as the overlay content).
|
||||||
return Some(overlay::Element::new(Box::new(FlyoutOverlay {
|
return Some(overlay::Element::new(Box::new(FlyoutOverlay {
|
||||||
content: &mut self.panels[p].elements[Level::Full as usize],
|
content: self.panels[p].elements[Level::Full as usize]
|
||||||
|
.as_mut()
|
||||||
|
.expect("open collapsed flyout has full panel content"),
|
||||||
tree: &mut tree.children[slot(p, Level::Full)],
|
tree: &mut tree.children[slot(p, Level::Full)],
|
||||||
anchor,
|
anchor,
|
||||||
})));
|
})));
|
||||||
@@ -595,7 +654,9 @@ impl<'a> Widget<Message, Theme, Renderer> for CollapsePanels<'a> {
|
|||||||
let (chunk, rest) = tree_rest.split_at_mut(SLOTS);
|
let (chunk, rest) = tree_rest.split_at_mut(SLOTS);
|
||||||
tree_rest = rest;
|
tree_rest = rest;
|
||||||
let level = levels.get(i).copied().unwrap_or(Level::Full);
|
let level = levels.get(i).copied().unwrap_or(Level::Full);
|
||||||
let child = &mut panel.elements[level as usize];
|
let Some(child) = panel.elements[level as usize].as_mut() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
if let Some(o) = child.as_widget_mut().overlay(
|
if let Some(o) = child.as_widget_mut().overlay(
|
||||||
&mut chunk[level as usize],
|
&mut chunk[level as usize],
|
||||||
child_layout,
|
child_layout,
|
||||||
@@ -753,6 +814,76 @@ mod tests {
|
|||||||
assert!(!cache.matches(17, 1));
|
assert!(!cache.matches(17, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn auto_builds_all_densities_once_then_only_the_selected_density() {
|
||||||
|
use iced_test::runtime::user_interface::{Cache, UserInterface};
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
fn view(counter: Arc<AtomicUsize>, open: Option<String>) -> Element<'static, Message> {
|
||||||
|
let panel = Panel::deferred("test", move |level| {
|
||||||
|
counter.fetch_add(1, Ordering::Relaxed);
|
||||||
|
let (width, height) = match level {
|
||||||
|
Level::Full => (200.0, 80.0),
|
||||||
|
Level::Compact => (150.0, 70.0),
|
||||||
|
Level::Collapsed => (100.0, 50.0),
|
||||||
|
Level::Tight => (50.0, 30.0),
|
||||||
|
};
|
||||||
|
iced::widget::container(iced::widget::text("panel"))
|
||||||
|
.width(Length::Fixed(width))
|
||||||
|
.height(Length::Fixed(height))
|
||||||
|
.into()
|
||||||
|
});
|
||||||
|
CollapsePanels::new(vec![panel], open, 80.0)
|
||||||
|
.measure_key(17)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
let count = Arc::new(AtomicUsize::new(0));
|
||||||
|
let mut renderer = iced_test::futures::futures::executor::block_on(
|
||||||
|
<Renderer as iced_test::core::renderer::Headless>::new(
|
||||||
|
iced_test::core::renderer::Settings::default(),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.expect("headless renderer");
|
||||||
|
|
||||||
|
let ui = UserInterface::build(
|
||||||
|
view(count.clone(), None),
|
||||||
|
Size::new(1000.0, 100.0),
|
||||||
|
Cache::default(),
|
||||||
|
&mut renderer,
|
||||||
|
);
|
||||||
|
assert_eq!(count.load(Ordering::Relaxed), 4);
|
||||||
|
|
||||||
|
let ui = UserInterface::build(
|
||||||
|
view(count.clone(), None),
|
||||||
|
Size::new(1000.0, 100.0),
|
||||||
|
ui.into_cache(),
|
||||||
|
&mut renderer,
|
||||||
|
);
|
||||||
|
assert_eq!(count.load(Ordering::Relaxed), 5, "only Full is rebuilt");
|
||||||
|
|
||||||
|
let ui = UserInterface::build(
|
||||||
|
view(count.clone(), None),
|
||||||
|
Size::new(170.0, 100.0),
|
||||||
|
ui.into_cache(),
|
||||||
|
&mut renderer,
|
||||||
|
);
|
||||||
|
assert_eq!(count.load(Ordering::Relaxed), 6, "resize builds Compact");
|
||||||
|
|
||||||
|
let _ui = UserInterface::build(
|
||||||
|
view(count.clone(), Some("test".to_string())),
|
||||||
|
Size::new(20.0, 100.0),
|
||||||
|
ui.into_cache(),
|
||||||
|
&mut renderer,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
count.load(Ordering::Relaxed),
|
||||||
|
8,
|
||||||
|
"tight selection builds Tight and Full for the open flyout"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn w(full: f32, compact: f32, button: f32, tight: f32) -> Widths {
|
fn w(full: f32, compact: f32, button: f32, tight: f32) -> Widths {
|
||||||
Widths {
|
Widths {
|
||||||
full,
|
full,
|
||||||
|
|||||||
+24
-40
@@ -632,16 +632,13 @@ impl Ribbon {
|
|||||||
&'a self,
|
&'a self,
|
||||||
g: &'a RibbonGroup,
|
g: &'a RibbonGroup,
|
||||||
ts: widgets::ToggleState,
|
ts: widgets::ToggleState,
|
||||||
style_ctx: &StyleContext<'_>,
|
style_ctx: StyleContext<'a>,
|
||||||
lead: &dyn Fn(bool) -> Vec<Element<'a, Message>>,
|
|
||||||
mode: CollapseMode,
|
|
||||||
flyout_open: bool,
|
|
||||||
) -> Panel<'a> {
|
) -> Panel<'a> {
|
||||||
let group = |compact: bool| {
|
let group = move |compact: bool| {
|
||||||
render_group(
|
render_group(
|
||||||
compact,
|
compact,
|
||||||
g,
|
g,
|
||||||
lead(compact),
|
Vec::new(),
|
||||||
&self.active_tool,
|
&self.active_tool,
|
||||||
&self.open_dropdown,
|
&self.open_dropdown,
|
||||||
&self.last_cmd,
|
&self.last_cmd,
|
||||||
@@ -651,10 +648,10 @@ impl Ribbon {
|
|||||||
self.active_color,
|
self.active_color,
|
||||||
&self.active_linetype,
|
&self.active_linetype,
|
||||||
self.active_lineweight,
|
self.active_lineweight,
|
||||||
style_ctx,
|
&style_ctx,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let button = |tight: bool| {
|
let button = move |tight: bool| {
|
||||||
collapse_button(
|
collapse_button(
|
||||||
g,
|
g,
|
||||||
self.last_panel_tool.get(g.title).copied(),
|
self.last_panel_tool.get(g.title).copied(),
|
||||||
@@ -667,26 +664,16 @@ impl Ribbon {
|
|||||||
self.active_color,
|
self.active_color,
|
||||||
&self.active_linetype,
|
&self.active_linetype,
|
||||||
self.active_lineweight,
|
self.active_lineweight,
|
||||||
style_ctx,
|
&style_ctx,
|
||||||
tight,
|
tight,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let empty = || text("").into();
|
Panel::deferred(g.title, move |level| match level {
|
||||||
let elements = match mode {
|
collapse::Level::Full => group(false),
|
||||||
CollapseMode::Auto => [group(false), group(true), button(false), button(true)],
|
collapse::Level::Compact => group(true),
|
||||||
CollapseMode::Full => [group(false), empty(), empty(), empty()],
|
collapse::Level::Collapsed => button(false),
|
||||||
CollapseMode::Compact => [empty(), group(true), empty(), empty()],
|
collapse::Level::Tight => button(true),
|
||||||
CollapseMode::Collapsed => [
|
})
|
||||||
if flyout_open { group(false) } else { empty() },
|
|
||||||
empty(),
|
|
||||||
button(false),
|
|
||||||
empty(),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
Panel {
|
|
||||||
id: g.title.to_string(),
|
|
||||||
elements,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_dropdown(&mut self, id: &str) {
|
pub fn toggle_dropdown(&mut self, id: &str) {
|
||||||
@@ -981,13 +968,17 @@ impl Ribbon {
|
|||||||
// shrink to compact icon columns, then it collapses to a ▾ flyout
|
// shrink to compact icon columns, then it collapses to a ▾ flyout
|
||||||
// button. See `CollapsePanels`.
|
// button. See `CollapsePanels`.
|
||||||
let ts = self.toggle_state(show_block_palette);
|
let ts = self.toggle_state(show_block_palette);
|
||||||
let measure_key = panel_measure_key(
|
let measure_key = if self.collapse_mode == CollapseMode::Auto {
|
||||||
&groups,
|
panel_measure_key(
|
||||||
&self.last_cmd,
|
&groups,
|
||||||
&self.last_panel_tool,
|
&self.last_cmd,
|
||||||
&style_ctx,
|
&self.last_panel_tool,
|
||||||
visibility.as_ref(),
|
&style_ctx,
|
||||||
);
|
visibility.as_ref(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
let panels: Vec<Panel<'_>> = groups
|
let panels: Vec<Panel<'_>> = groups
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|g| {
|
.map(|g| {
|
||||||
@@ -996,14 +987,7 @@ impl Ribbon {
|
|||||||
{
|
{
|
||||||
visibility_panel::panel(visibility.clone())
|
visibility_panel::panel(visibility.clone())
|
||||||
} else {
|
} else {
|
||||||
self.panel(
|
self.panel(g, ts, style_ctx)
|
||||||
g,
|
|
||||||
ts,
|
|
||||||
&style_ctx,
|
|
||||||
&|_| Vec::new(),
|
|
||||||
self.collapse_mode,
|
|
||||||
self.collapsed_open.as_deref() == Some(g.title),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
use iced::widget::{button, column, container, pick_list, row, text, tooltip};
|
use iced::widget::{button, column, container, pick_list, row, text, tooltip};
|
||||||
use iced::{Element, Fill, Theme};
|
use iced::{Element, Fill, Theme};
|
||||||
|
|
||||||
use super::collapse::Panel;
|
use super::collapse::{Level, Panel};
|
||||||
use super::widgets::{make_icon, tool_btn_style, LARGE_ICON, LARGE_W, SMALL_ICON, TOOL_BAR_H};
|
use super::widgets::{LARGE_ICON, LARGE_W, SMALL_ICON, TOOL_BAR_H, make_icon, tool_btn_style};
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
use crate::modules::IconKind;
|
use crate::modules::IconKind;
|
||||||
use crate::ui::window::block_visibility::{Action, RibbonContext};
|
|
||||||
use crate::ui::ROW_H;
|
use crate::ui::ROW_H;
|
||||||
|
use crate::ui::window::block_visibility::{Action, RibbonContext};
|
||||||
|
|
||||||
fn tool(
|
fn tool(
|
||||||
id: &'static str,
|
id: &'static str,
|
||||||
@@ -152,14 +152,11 @@ fn collapsed(tight: bool) -> Element<'static, Message> {
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn panel(context: Option<RibbonContext>) -> Panel<'static> {
|
pub(super) fn panel<'a>(context: Option<RibbonContext>) -> Panel<'a> {
|
||||||
Panel {
|
Panel::deferred("Visibility", move |level| match level {
|
||||||
id: "Visibility".into(),
|
Level::Full => view(context.clone(), false),
|
||||||
elements: [
|
Level::Compact => view(context.clone(), true),
|
||||||
view(context.clone(), false),
|
Level::Collapsed => collapsed(false),
|
||||||
view(context, true),
|
Level::Tight => collapsed(true),
|
||||||
collapsed(false),
|
})
|
||||||
collapsed(true),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -321,6 +321,7 @@ pub(super) const PROP_LW_ID: &str = "PROP_LW";
|
|||||||
|
|
||||||
// ── Style context (passed from Ribbon to render_large) ────────────────────
|
// ── Style context (passed from Ribbon to render_large) ────────────────────
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
pub(super) struct StyleContext<'a> {
|
pub(super) struct StyleContext<'a> {
|
||||||
pub text_style_names: &'a [String],
|
pub text_style_names: &'a [String],
|
||||||
pub active_text_style: &'a str,
|
pub active_text_style: &'a str,
|
||||||
|
|||||||
Reference in new issue
Block a user