forked from 1b-agent/opencadstudio-upstream
perf(ribbon): cache natural panel measurements
This commit is contained in:
1 parent
42ba7f12a0
commit
140303217f
2 files changed
+352
-60
No files matched your search
+95
-38
@@ -8,12 +8,12 @@
|
|||||||
//! panel, so it shrinks as the panels do.
|
//! panel, so it shrinks as the panels do.
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||||
|
|
||||||
use iced::advanced::layout::{self, Layout};
|
use iced::advanced::layout::{self, Layout};
|
||||||
use iced::advanced::widget::{self, Widget};
|
use iced::advanced::widget::{self, Widget};
|
||||||
use iced::advanced::{mouse, overlay, renderer, Renderer as _, Shell};
|
use iced::advanced::{Renderer as _, Shell, mouse, overlay, renderer};
|
||||||
use iced::{
|
use iced::{
|
||||||
Background, Border, Element, Event, Length, Point, Rectangle, Renderer, Shadow, Size, Theme,
|
Background, Border, Element, Event, Length, Point, Rectangle, Renderer, Shadow, Size, Theme,
|
||||||
Vector,
|
Vector,
|
||||||
@@ -173,6 +173,23 @@ pub(crate) struct CollapsePanels<'a> {
|
|||||||
/// User-chosen density. `Auto` runs the width-based degradation; the others
|
/// User-chosen density. `Auto` runs the width-based degradation; the others
|
||||||
/// pin every panel to one level regardless of the window width.
|
/// pin every panel to one level regardless of the window width.
|
||||||
mode: CollapseMode,
|
mode: CollapseMode,
|
||||||
|
/// Fingerprint of content that can affect natural panel dimensions.
|
||||||
|
measure_key: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct MeasureCache {
|
||||||
|
key: Option<u64>,
|
||||||
|
widths: Vec<Widths>,
|
||||||
|
heights: Vec<[f32; 4]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MeasureCache {
|
||||||
|
fn matches(&self, key: u64, panel_count: usize) -> bool {
|
||||||
|
self.key == Some(key)
|
||||||
|
&& self.widths.len() == panel_count
|
||||||
|
&& self.heights.len() == panel_count
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CollapsePanels<'a> {
|
impl<'a> CollapsePanels<'a> {
|
||||||
@@ -186,9 +203,18 @@ impl<'a> CollapsePanels<'a> {
|
|||||||
height_out: None,
|
height_out: None,
|
||||||
tight_out: None,
|
tight_out: None,
|
||||||
mode: CollapseMode::Auto,
|
mode: CollapseMode::Auto,
|
||||||
|
measure_key: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reuse natural measurements while the panel content and font metrics stay
|
||||||
|
/// unchanged. The available window width is intentionally not part of the
|
||||||
|
/// key: these measurements use unbounded natural limits.
|
||||||
|
pub fn measure_key(mut self, key: u64) -> Self {
|
||||||
|
self.measure_key = key;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Report the measured row height into `out` on every layout.
|
/// Report the measured row height into `out` on every layout.
|
||||||
pub fn report_height(mut self, out: Arc<AtomicU32>) -> Self {
|
pub fn report_height(mut self, out: Arc<AtomicU32>) -> Self {
|
||||||
self.height_out = Some(out);
|
self.height_out = Some(out);
|
||||||
@@ -225,6 +251,14 @@ impl<'a> CollapsePanels<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Widget<Message, Theme, Renderer> for CollapsePanels<'a> {
|
impl<'a> Widget<Message, Theme, Renderer> for CollapsePanels<'a> {
|
||||||
|
fn tag(&self) -> widget::tree::Tag {
|
||||||
|
widget::tree::Tag::of::<MeasureCache>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state(&self) -> widget::tree::State {
|
||||||
|
widget::tree::State::new(MeasureCache::default())
|
||||||
|
}
|
||||||
|
|
||||||
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 mut refs = Vec::with_capacity(self.panels.len() * SLOTS);
|
||||||
for p in &mut self.panels {
|
for p in &mut self.panels {
|
||||||
@@ -254,42 +288,52 @@ impl<'a> Widget<Message, Theme, Renderer> for CollapsePanels<'a> {
|
|||||||
let mut heights = Vec::with_capacity(n);
|
let mut heights = Vec::with_capacity(n);
|
||||||
let auto = self.mode == CollapseMode::Auto;
|
let auto = self.mode == CollapseMode::Auto;
|
||||||
if auto {
|
if auto {
|
||||||
for i in 0..n {
|
let cache = tree.state.downcast_ref::<MeasureCache>();
|
||||||
let f = self.panels[i].elements[Level::Full as usize]
|
if cache.matches(self.measure_key, n) {
|
||||||
.as_widget_mut()
|
widths.clone_from(&cache.widths);
|
||||||
.layout(&mut tree.children[slot(i, Level::Full)], renderer, &natural)
|
heights.clone_from(&cache.heights);
|
||||||
.size();
|
} else {
|
||||||
let c = self.panels[i].elements[Level::Compact as usize]
|
for i in 0..n {
|
||||||
.as_widget_mut()
|
let f = self.panels[i].elements[Level::Full as usize]
|
||||||
.layout(
|
.as_widget_mut()
|
||||||
&mut tree.children[slot(i, Level::Compact)],
|
.layout(&mut tree.children[slot(i, Level::Full)], renderer, &natural)
|
||||||
renderer,
|
.size();
|
||||||
&natural,
|
let c = self.panels[i].elements[Level::Compact as usize]
|
||||||
)
|
.as_widget_mut()
|
||||||
.size();
|
.layout(
|
||||||
let b = self.panels[i].elements[Level::Collapsed as usize]
|
&mut tree.children[slot(i, Level::Compact)],
|
||||||
.as_widget_mut()
|
renderer,
|
||||||
.layout(
|
&natural,
|
||||||
&mut tree.children[slot(i, Level::Collapsed)],
|
)
|
||||||
renderer,
|
.size();
|
||||||
&natural,
|
let b = self.panels[i].elements[Level::Collapsed as usize]
|
||||||
)
|
.as_widget_mut()
|
||||||
.size();
|
.layout(
|
||||||
let t = self.panels[i].elements[Level::Tight as usize]
|
&mut tree.children[slot(i, Level::Collapsed)],
|
||||||
.as_widget_mut()
|
renderer,
|
||||||
.layout(
|
&natural,
|
||||||
&mut tree.children[slot(i, Level::Tight)],
|
)
|
||||||
renderer,
|
.size();
|
||||||
&natural,
|
let t = self.panels[i].elements[Level::Tight as usize]
|
||||||
)
|
.as_widget_mut()
|
||||||
.size();
|
.layout(
|
||||||
widths.push(Widths {
|
&mut tree.children[slot(i, Level::Tight)],
|
||||||
full: f.width,
|
renderer,
|
||||||
compact: c.width,
|
&natural,
|
||||||
button: b.width,
|
)
|
||||||
tight: t.width,
|
.size();
|
||||||
});
|
widths.push(Widths {
|
||||||
heights.push([f.height, c.height, b.height, t.height]);
|
full: f.width,
|
||||||
|
compact: c.width,
|
||||||
|
button: b.width,
|
||||||
|
tight: t.width,
|
||||||
|
});
|
||||||
|
heights.push([f.height, c.height, b.height, t.height]);
|
||||||
|
}
|
||||||
|
let cache = tree.state.downcast_mut::<MeasureCache>();
|
||||||
|
cache.key = Some(self.measure_key);
|
||||||
|
cache.widths.clone_from(&widths);
|
||||||
|
cache.heights.clone_from(&heights);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let levels = if let Some(level) = self.mode.forced_level() {
|
let levels = if let Some(level) = self.mode.forced_level() {
|
||||||
@@ -696,6 +740,19 @@ impl overlay::Overlay<Message, Theme, Renderer> for FlyoutOverlay<'_, '_> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn natural_measurements_are_reused_only_for_the_same_key_and_panel_count() {
|
||||||
|
let cache = MeasureCache {
|
||||||
|
key: Some(17),
|
||||||
|
widths: vec![Widths::default(); 2],
|
||||||
|
heights: vec![[0.0; 4]; 2],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(cache.matches(17, 2));
|
||||||
|
assert!(!cache.matches(18, 2));
|
||||||
|
assert!(!cache.matches(17, 1));
|
||||||
|
}
|
||||||
|
|
||||||
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,
|
||||||
|
|||||||
+257
-22
@@ -6,8 +6,10 @@
|
|||||||
//
|
//
|
||||||
// Dropdown items within a group are collected into columns of 3 rows.
|
// Dropdown items within a group are collected into columns of 3 rows.
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||||
|
|
||||||
use rustc_hash::FxHashMap as HashMap;
|
use rustc_hash::FxHashMap as HashMap;
|
||||||
|
|
||||||
@@ -16,9 +18,9 @@ use iced::widget::{button, column, container, mouse_area, row, scrollable, text}
|
|||||||
use iced::{Background, Border, Color, Element, Fill, Length, Padding, Theme};
|
use iced::{Background, Border, Color, Element, Fill, Length, Padding, Theme};
|
||||||
|
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
use crate::modules::{CadModule, IconKind, RibbonGroup, RibbonItem};
|
use crate::modules::{CadModule, IconKind, RibbonGroup, RibbonItem, ToolDef};
|
||||||
use crate::plugin::all_ribbon_modules;
|
use crate::plugin::all_ribbon_modules;
|
||||||
use crate::ui::properties::{linetype_display_name, lw_options, LinetypeItem};
|
use crate::ui::properties::{LinetypeItem, linetype_display_name, lw_options};
|
||||||
|
|
||||||
mod draw_panel;
|
mod draw_panel;
|
||||||
pub(crate) mod widgets;
|
pub(crate) mod widgets;
|
||||||
@@ -27,9 +29,9 @@ mod color_dropdown;
|
|||||||
mod context_tools;
|
mod context_tools;
|
||||||
mod modify_panel;
|
mod modify_panel;
|
||||||
mod visibility_panel;
|
mod visibility_panel;
|
||||||
pub use context_tools::{pdf_underlay_tools, point_cloud_tools, xref_tools, UnderlayContext};
|
pub use context_tools::{UnderlayContext, pdf_underlay_tools, point_cloud_tools, xref_tools};
|
||||||
use widgets::{StyleContext, *};
|
|
||||||
pub(crate) use widgets::{REDO_HISTORY_ID, UNDO_HISTORY_ID};
|
pub(crate) use widgets::{REDO_HISTORY_ID, UNDO_HISTORY_ID};
|
||||||
|
use widgets::{StyleContext, *};
|
||||||
mod collapse;
|
mod collapse;
|
||||||
mod keytips;
|
mod keytips;
|
||||||
mod locate;
|
mod locate;
|
||||||
@@ -38,9 +40,8 @@ use crate::ui::wrap_bar::{PosReport, WrapBar, WrapFlow};
|
|||||||
pub use collapse::CollapseMode;
|
pub use collapse::CollapseMode;
|
||||||
use collapse::{CollapsePanels, Panel};
|
use collapse::{CollapsePanels, Panel};
|
||||||
pub use keytips::{
|
pub use keytips::{
|
||||||
active as keytips_active, key_pressed as keytip_key_pressed,
|
KeyTipInput, KeyTipStep, active as keytips_active, key_pressed as keytip_key_pressed,
|
||||||
key_released as keytip_key_released, pointer_pressed as keytip_pointer_pressed, KeyTipInput,
|
key_released as keytip_key_released, pointer_pressed as keytip_pointer_pressed,
|
||||||
KeyTipStep,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) fn tooltip_content(text: String) -> Element<'static, Message> {
|
pub(crate) fn tooltip_content(text: String) -> Element<'static, Message> {
|
||||||
@@ -56,6 +57,144 @@ fn dropdown_header(id: &str) -> Option<&'static str> {
|
|||||||
(id == "IMPORT_FILE").then_some("Import File — the face runs the last one chosen")
|
(id == "IMPORT_FILE").then_some("Import File — the face runs the last one chosen")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn hash_tool_layout(tool: &ToolDef, hasher: &mut impl Hasher) {
|
||||||
|
tool.id.hash(hasher);
|
||||||
|
t!(tool.label).as_ref().hash(hasher);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_item_layout(item: &RibbonItem, hasher: &mut impl Hasher) {
|
||||||
|
match item {
|
||||||
|
RibbonItem::Tool(tool) => {
|
||||||
|
0u8.hash(hasher);
|
||||||
|
hash_tool_layout(tool, hasher);
|
||||||
|
}
|
||||||
|
RibbonItem::LabeledTool(tool) => {
|
||||||
|
1u8.hash(hasher);
|
||||||
|
hash_tool_layout(tool, hasher);
|
||||||
|
}
|
||||||
|
RibbonItem::LargeTool(tool) => {
|
||||||
|
2u8.hash(hasher);
|
||||||
|
hash_tool_layout(tool, hasher);
|
||||||
|
}
|
||||||
|
RibbonItem::Dropdown { id, items, .. } => {
|
||||||
|
3u8.hash(hasher);
|
||||||
|
id.hash(hasher);
|
||||||
|
items.len().hash(hasher);
|
||||||
|
for (command, label, _) in items {
|
||||||
|
command.hash(hasher);
|
||||||
|
t!(*label).as_ref().hash(hasher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RibbonItem::LabeledDropdown {
|
||||||
|
id, label, items, ..
|
||||||
|
} => {
|
||||||
|
4u8.hash(hasher);
|
||||||
|
id.hash(hasher);
|
||||||
|
t!(*label).as_ref().hash(hasher);
|
||||||
|
items.len().hash(hasher);
|
||||||
|
for (command, item_label, _) in items {
|
||||||
|
command.hash(hasher);
|
||||||
|
t!(*item_label).as_ref().hash(hasher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RibbonItem::LargeDropdown {
|
||||||
|
id, label, items, ..
|
||||||
|
} => {
|
||||||
|
5u8.hash(hasher);
|
||||||
|
id.hash(hasher);
|
||||||
|
t!(*label).as_ref().hash(hasher);
|
||||||
|
items.len().hash(hasher);
|
||||||
|
for (command, item_label, _) in items {
|
||||||
|
command.hash(hasher);
|
||||||
|
t!(*item_label).as_ref().hash(hasher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RibbonItem::ToolGrid { columns } => {
|
||||||
|
6u8.hash(hasher);
|
||||||
|
columns.len().hash(hasher);
|
||||||
|
for column in columns {
|
||||||
|
column.len().hash(hasher);
|
||||||
|
for tool in column {
|
||||||
|
hash_tool_layout(tool, hasher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RibbonItem::LayerComboGroup { row2, row3 } => {
|
||||||
|
7u8.hash(hasher);
|
||||||
|
row2.len().hash(hasher);
|
||||||
|
row3.len().hash(hasher);
|
||||||
|
for tool in row2.iter().chain(row3) {
|
||||||
|
hash_tool_layout(tool, hasher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RibbonItem::PropertiesGroup { match_prop } => {
|
||||||
|
8u8.hash(hasher);
|
||||||
|
hash_tool_layout(match_prop, hasher);
|
||||||
|
}
|
||||||
|
RibbonItem::StyleComboGroup {
|
||||||
|
style_key,
|
||||||
|
combo_id,
|
||||||
|
rows,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
9u8.hash(hasher);
|
||||||
|
format!("{style_key:?}").hash(hasher);
|
||||||
|
combo_id.hash(hasher);
|
||||||
|
for row in rows {
|
||||||
|
row.len().hash(hasher);
|
||||||
|
for tool in row {
|
||||||
|
hash_tool_layout(tool, hasher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn panel_measure_key(
|
||||||
|
groups: &[&RibbonGroup],
|
||||||
|
last_cmd: &HashMap<&'static str, &'static str>,
|
||||||
|
last_panel_tool: &HashMap<&'static str, &'static str>,
|
||||||
|
style_ctx: &StyleContext<'_>,
|
||||||
|
visibility: Option<&crate::ui::window::block_visibility::RibbonContext>,
|
||||||
|
) -> u64 {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
crate::sys::ui_font::generation().hash(&mut hasher);
|
||||||
|
groups.len().hash(&mut hasher);
|
||||||
|
for group in groups {
|
||||||
|
group.title.hash(&mut hasher);
|
||||||
|
group.tools.len().hash(&mut hasher);
|
||||||
|
for item in &group.tools {
|
||||||
|
hash_item_layout(item, &mut hasher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut commands: Vec<_> = last_cmd.iter().collect();
|
||||||
|
commands.sort_unstable_by_key(|(id, _)| **id);
|
||||||
|
for (id, command) in commands {
|
||||||
|
id.hash(&mut hasher);
|
||||||
|
command.hash(&mut hasher);
|
||||||
|
}
|
||||||
|
let mut representatives: Vec<_> = last_panel_tool.iter().collect();
|
||||||
|
representatives.sort_unstable_by_key(|(title, _)| **title);
|
||||||
|
for (title, tool) in representatives {
|
||||||
|
title.hash(&mut hasher);
|
||||||
|
tool.hash(&mut hasher);
|
||||||
|
}
|
||||||
|
for (names, active) in [
|
||||||
|
(style_ctx.text_style_names, style_ctx.active_text_style),
|
||||||
|
(style_ctx.dim_style_names, style_ctx.active_dim_style),
|
||||||
|
(
|
||||||
|
style_ctx.mleader_style_names,
|
||||||
|
style_ctx.active_mleader_style,
|
||||||
|
),
|
||||||
|
(style_ctx.table_style_names, style_ctx.active_table_style),
|
||||||
|
] {
|
||||||
|
names.hash(&mut hasher);
|
||||||
|
active.hash(&mut hasher);
|
||||||
|
}
|
||||||
|
format!("{visibility:?}").hash(&mut hasher);
|
||||||
|
hasher.finish()
|
||||||
|
}
|
||||||
|
|
||||||
// ── Ribbon state ───────────────────────────────────────────────────────────
|
// ── Ribbon state ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
pub struct Ribbon {
|
pub struct Ribbon {
|
||||||
@@ -495,6 +634,8 @@ impl Ribbon {
|
|||||||
ts: widgets::ToggleState,
|
ts: widgets::ToggleState,
|
||||||
style_ctx: &StyleContext<'_>,
|
style_ctx: &StyleContext<'_>,
|
||||||
lead: &dyn Fn(bool) -> Vec<Element<'a, Message>>,
|
lead: &dyn Fn(bool) -> Vec<Element<'a, Message>>,
|
||||||
|
mode: CollapseMode,
|
||||||
|
flyout_open: bool,
|
||||||
) -> Panel<'a> {
|
) -> Panel<'a> {
|
||||||
let group = |compact: bool| {
|
let group = |compact: bool| {
|
||||||
render_group(
|
render_group(
|
||||||
@@ -530,9 +671,21 @@ impl Ribbon {
|
|||||||
tight,
|
tight,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
let empty = || text("").into();
|
||||||
|
let elements = match mode {
|
||||||
|
CollapseMode::Auto => [group(false), group(true), button(false), button(true)],
|
||||||
|
CollapseMode::Full => [group(false), empty(), empty(), empty()],
|
||||||
|
CollapseMode::Compact => [empty(), group(true), empty(), empty()],
|
||||||
|
CollapseMode::Collapsed => [
|
||||||
|
if flyout_open { group(false) } else { empty() },
|
||||||
|
empty(),
|
||||||
|
button(false),
|
||||||
|
empty(),
|
||||||
|
],
|
||||||
|
};
|
||||||
Panel {
|
Panel {
|
||||||
id: g.title.to_string(),
|
id: g.title.to_string(),
|
||||||
elements: [group(false), group(true), button(false), button(true)],
|
elements,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -773,12 +926,14 @@ impl Ribbon {
|
|||||||
.padding([2, 8]);
|
.padding([2, 8]);
|
||||||
let mode_dd = PosReport::new(COLLAPSE_MODE_ID, mode_btn);
|
let mode_dd = PosReport::new(COLLAPSE_MODE_ID, mode_btn);
|
||||||
|
|
||||||
let mut tab_row = row![container(
|
let mut tab_row = row![
|
||||||
WrapBar::new(lead.into(), tabs.into())
|
container(
|
||||||
.spacing(6.0)
|
WrapBar::new(lead.into(), tabs.into())
|
||||||
.report_height(self.tab_bar_h.clone()),
|
.spacing(6.0)
|
||||||
)
|
.report_height(self.tab_bar_h.clone()),
|
||||||
.width(Length::Fill)]
|
)
|
||||||
|
.width(Length::Fill)
|
||||||
|
]
|
||||||
.spacing(6.0)
|
.spacing(6.0)
|
||||||
.align_y(iced::Center);
|
.align_y(iced::Center);
|
||||||
if !self.collapse_tight.load(Ordering::Relaxed) {
|
if !self.collapse_tight.load(Ordering::Relaxed) {
|
||||||
@@ -809,7 +964,7 @@ impl Ribbon {
|
|||||||
self.active
|
self.active
|
||||||
};
|
};
|
||||||
let tool_area: Element<'_, Message> = if self.modules.get(effective_active).is_some() {
|
let tool_area: Element<'_, Message> = if self.modules.get(effective_active).is_some() {
|
||||||
let groups = self.groups_for(effective_active);
|
let groups: Vec<_> = self.groups_for(effective_active).collect();
|
||||||
let style_ctx = StyleContext {
|
let style_ctx = StyleContext {
|
||||||
text_style_names: &self.text_style_names,
|
text_style_names: &self.text_style_names,
|
||||||
active_text_style: &self.active_text_style,
|
active_text_style: &self.active_text_style,
|
||||||
@@ -826,18 +981,34 @@ 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(
|
||||||
|
&groups,
|
||||||
|
&self.last_cmd,
|
||||||
|
&self.last_panel_tool,
|
||||||
|
&style_ctx,
|
||||||
|
visibility.as_ref(),
|
||||||
|
);
|
||||||
let panels: Vec<Panel<'_>> = groups
|
let panels: Vec<Panel<'_>> = groups
|
||||||
|
.into_iter()
|
||||||
.map(|g| {
|
.map(|g| {
|
||||||
if self.modules[effective_active].id() == "block_editor"
|
if self.modules[effective_active].id() == "block_editor"
|
||||||
&& g.title == "Visibility"
|
&& g.title == "Visibility"
|
||||||
{
|
{
|
||||||
visibility_panel::panel(visibility.clone())
|
visibility_panel::panel(visibility.clone())
|
||||||
} else {
|
} else {
|
||||||
self.panel(g, ts, &style_ctx, &|_| Vec::new())
|
self.panel(
|
||||||
|
g,
|
||||||
|
ts,
|
||||||
|
&style_ctx,
|
||||||
|
&|_| Vec::new(),
|
||||||
|
self.collapse_mode,
|
||||||
|
self.collapsed_open.as_deref() == Some(g.title),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
CollapsePanels::new(panels, self.collapsed_open.clone(), TOOL_BAR_H)
|
CollapsePanels::new(panels, self.collapsed_open.clone(), TOOL_BAR_H)
|
||||||
|
.measure_key(measure_key)
|
||||||
.report_height(self.tool_bar_h.clone())
|
.report_height(self.tool_bar_h.clone())
|
||||||
.report_tight(self.collapse_tight.clone())
|
.report_tight(self.collapse_tight.clone())
|
||||||
.mode(self.collapse_mode)
|
.mode(self.collapse_mode)
|
||||||
@@ -1780,7 +1951,69 @@ impl Default for Ribbon {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::widgets::is_active_tool;
|
use super::widgets::is_active_tool;
|
||||||
use super::Ribbon;
|
use super::{CollapseMode, Ribbon, StyleContext, panel_measure_key};
|
||||||
|
use crate::modules::{IconKind, ModuleEvent, RibbonGroup, RibbonItem, ToolDef};
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
|
fn measurement_key(tool_id: &'static str, selected_command: &'static str) -> u64 {
|
||||||
|
let groups = [RibbonGroup {
|
||||||
|
title: "Test",
|
||||||
|
tools: vec![RibbonItem::LargeTool(ToolDef {
|
||||||
|
id: tool_id,
|
||||||
|
label: "Test Tool",
|
||||||
|
icon: IconKind::Glyph("T"),
|
||||||
|
event: ModuleEvent::Command("TEST".to_string()),
|
||||||
|
})],
|
||||||
|
}];
|
||||||
|
let style_ctx = StyleContext {
|
||||||
|
text_style_names: &[],
|
||||||
|
active_text_style: "",
|
||||||
|
dim_style_names: &[],
|
||||||
|
active_dim_style: "",
|
||||||
|
mleader_style_names: &[],
|
||||||
|
active_mleader_style: "",
|
||||||
|
table_style_names: &[],
|
||||||
|
active_table_style: "",
|
||||||
|
};
|
||||||
|
let groups: Vec<_> = groups.iter().collect();
|
||||||
|
let mut last_cmd = FxHashMap::default();
|
||||||
|
last_cmd.insert("TEST_DROPDOWN", selected_command);
|
||||||
|
panel_measure_key(&groups, &last_cmd, &FxHashMap::default(), &style_ctx, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn panel_measure_fingerprint_is_stable_and_tracks_panel_structure() {
|
||||||
|
assert_eq!(
|
||||||
|
measurement_key("TEST", "TEST"),
|
||||||
|
measurement_key("TEST", "TEST")
|
||||||
|
);
|
||||||
|
assert_ne!(
|
||||||
|
measurement_key("TEST", "TEST"),
|
||||||
|
measurement_key("OTHER", "TEST")
|
||||||
|
);
|
||||||
|
assert_ne!(
|
||||||
|
measurement_key("TEST", "TEST"),
|
||||||
|
measurement_key("TEST", "OTHER")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn forced_density_views_build_and_keep_collapsed_flyout_content() {
|
||||||
|
let mut ribbon = Ribbon::default();
|
||||||
|
let first_panel = ribbon.groups_for(ribbon.active).next().unwrap().title;
|
||||||
|
|
||||||
|
for mode in [
|
||||||
|
CollapseMode::Full,
|
||||||
|
CollapseMode::Compact,
|
||||||
|
CollapseMode::Collapsed,
|
||||||
|
] {
|
||||||
|
ribbon.set_collapse_mode(mode);
|
||||||
|
ribbon.collapsed_open =
|
||||||
|
(mode == CollapseMode::Collapsed).then(|| first_panel.to_string());
|
||||||
|
let view = ribbon.view(false, false, 0, 0, false, None);
|
||||||
|
std::hint::black_box(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn block_test_close_panel_extends_draw_only_while_testing() {
|
fn block_test_close_panel_extends_draw_only_while_testing() {
|
||||||
@@ -1790,10 +2023,12 @@ mod tests {
|
|||||||
.iter()
|
.iter()
|
||||||
.position(|module| module.id() == "draw")
|
.position(|module| module.id() == "draw")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(!ribbon
|
assert!(
|
||||||
.modules
|
!ribbon
|
||||||
.iter()
|
.modules
|
||||||
.any(|module| module.id() == "block_test"));
|
.iter()
|
||||||
|
.any(|module| module.id() == "block_test")
|
||||||
|
);
|
||||||
let ordinary: Vec<_> = ribbon.groups_for(draw).map(|group| group.title).collect();
|
let ordinary: Vec<_> = ribbon.groups_for(draw).map(|group| group.title).collect();
|
||||||
assert!(!ordinary.contains(&"Close"));
|
assert!(!ordinary.contains(&"Close"));
|
||||||
ribbon.set_block_editing(true);
|
ribbon.set_block_editing(true);
|
||||||
|
|||||||
Reference in new issue
Block a user