forked from 1b-agent/opencadstudio-upstream
feat(ribbon): wrap compact panels onto extra rows when still too narrow
DensitySwap now takes a third, narrowest variant: the compact panels flex-wrapped (via WrapFlow) across as many rows as the width needs. The tool area therefore degrades full -> compact -> wrapped as the window shrinks. build_tool_row becomes build_tool_groups, returning fixed-height panel elements so they lay out identically in a row or a wrap flow. The tool bar is no longer a fixed TOOL_BAR_H; DensitySwap reports its measured height into a cell and the ribbon dropdown overlays anchor at tab_bar_height() + tool_bar_height() so they track the taller bar. Second stage of the adaptive tool area (shrink -> wrap -> collapse). Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
65dfd4e462
commit
ff36a1bdac
2 files changed
+79
-55
No files matched your search
+64
-52
@@ -59,6 +59,9 @@ pub struct Ribbon {
|
||||
/// Measured tab-bar height (28 on one row, 56 when tabs wrap). Written by
|
||||
/// the `WrapBar` widget during layout, read when anchoring dropdowns.
|
||||
tab_bar_h: Arc<AtomicU32>,
|
||||
/// Measured tool-area height (TOOL_BAR_H on one row, taller when the panels
|
||||
/// wrap). Written by the `DensitySwap` widget, read when anchoring dropdowns.
|
||||
tool_bar_h: Arc<AtomicU32>,
|
||||
}
|
||||
|
||||
/// Per-layer display data shown in the ribbon layer dropdown.
|
||||
@@ -114,6 +117,7 @@ impl Ribbon {
|
||||
table_style_names: vec![],
|
||||
active_table_style: String::new(),
|
||||
tab_bar_h: Arc::new(AtomicU32::new(28.0f32.to_bits())),
|
||||
tool_bar_h: Arc::new(AtomicU32::new(TOOL_BAR_H.to_bits())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +126,11 @@ impl Ribbon {
|
||||
f32::from_bits(self.tab_bar_h.load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
/// Current tool-area height as last measured by the `DensitySwap` widget.
|
||||
fn tool_bar_height(&self) -> f32 {
|
||||
f32::from_bits(self.tool_bar_h.load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
pub fn set_styles(
|
||||
&mut self,
|
||||
text: Vec<String>,
|
||||
@@ -376,40 +385,45 @@ impl Ribbon {
|
||||
active_table_style: self.active_table_style.clone(),
|
||||
};
|
||||
|
||||
// Two density variants: full-size panels, and a compact one where
|
||||
// large buttons shrink to icon-only columns. DensitySwap shows the
|
||||
// widest that fits the current ribbon width.
|
||||
let full = build_tool_row(
|
||||
false,
|
||||
&groups,
|
||||
&self.active_tool,
|
||||
&self.open_dropdown,
|
||||
&self.last_cmd,
|
||||
self.wireframe,
|
||||
self.ortho_mode,
|
||||
&self.layer_infos,
|
||||
&self.active_layer,
|
||||
self.active_color,
|
||||
&self.active_linetype,
|
||||
self.active_lineweight,
|
||||
&style_ctx,
|
||||
);
|
||||
let compact = build_tool_row(
|
||||
true,
|
||||
&groups,
|
||||
&self.active_tool,
|
||||
&self.open_dropdown,
|
||||
&self.last_cmd,
|
||||
self.wireframe,
|
||||
self.ortho_mode,
|
||||
&self.layer_infos,
|
||||
&self.active_layer,
|
||||
self.active_color,
|
||||
&self.active_linetype,
|
||||
self.active_lineweight,
|
||||
&style_ctx,
|
||||
);
|
||||
DensitySwap::new(vec![full, compact]).into()
|
||||
// Adaptive tool area, widest→narrowest:
|
||||
// 1. full — full-size panels on one row
|
||||
// 2. compact — large buttons shrink to icon-only columns
|
||||
// 3. wrapped — compact panels flex-wrap onto extra rows
|
||||
// DensitySwap shows the widest variant that fits the width.
|
||||
let build = |compact: bool| {
|
||||
build_tool_groups(
|
||||
compact,
|
||||
&groups,
|
||||
&self.active_tool,
|
||||
&self.open_dropdown,
|
||||
&self.last_cmd,
|
||||
self.wireframe,
|
||||
self.ortho_mode,
|
||||
&self.layer_infos,
|
||||
&self.active_layer,
|
||||
self.active_color,
|
||||
&self.active_linetype,
|
||||
self.active_lineweight,
|
||||
&style_ctx,
|
||||
)
|
||||
};
|
||||
let full: Element<'_, Message> = build(false)
|
||||
.into_iter()
|
||||
.fold(row![].spacing(0).height(Length::Fixed(TOOL_BAR_H)), |r, w| {
|
||||
r.push(w)
|
||||
})
|
||||
.into();
|
||||
let compact: Element<'_, Message> = build(true)
|
||||
.into_iter()
|
||||
.fold(row![].spacing(0).height(Length::Fixed(TOOL_BAR_H)), |r, w| {
|
||||
r.push(w)
|
||||
})
|
||||
.into();
|
||||
let wrapped: Element<'_, Message> =
|
||||
WrapFlow::new(build(true)).spacing_x(0.0).row_h(TOOL_BAR_H).into();
|
||||
DensitySwap::new(vec![full, compact, wrapped])
|
||||
.report_height(self.tool_bar_h.clone())
|
||||
.into()
|
||||
} else {
|
||||
text("").into()
|
||||
};
|
||||
@@ -424,8 +438,7 @@ impl Ribbon {
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.width(Length::Fill)
|
||||
.height(TOOL_BAR_H);
|
||||
.width(Length::Fill);
|
||||
|
||||
column![tab_bar, tool_bar].into()
|
||||
}
|
||||
@@ -606,7 +619,7 @@ impl Ribbon {
|
||||
})
|
||||
.width(Length::Fixed(190.0));
|
||||
|
||||
let top_offset = self.tab_bar_height() + TOOL_BAR_H;
|
||||
let top_offset = self.tab_bar_height() + self.tool_bar_height();
|
||||
let left_offset = compute_dropdown_left(&groups, open_id);
|
||||
let positioned = container(panel)
|
||||
.align_left(Fill)
|
||||
@@ -729,7 +742,7 @@ impl Ribbon {
|
||||
})
|
||||
.width(Length::Fixed(220.0));
|
||||
|
||||
let top_offset = self.tab_bar_height() + TOOL_BAR_H;
|
||||
let top_offset = self.tab_bar_height() + self.tool_bar_height();
|
||||
let groups = self.modules.get(self.active)?.ribbon_groups();
|
||||
let left_offset = compute_layer_combo_left(&groups);
|
||||
let positioned = container(panel)
|
||||
@@ -853,7 +866,7 @@ impl Ribbon {
|
||||
})
|
||||
.width(Length::Fixed(LARGE_W * 2.3));
|
||||
|
||||
let top_offset = self.tab_bar_height() + TOOL_BAR_H;
|
||||
let top_offset = self.tab_bar_height() + self.tool_bar_height();
|
||||
let left_offset = compute_dropdown_left(&groups, open_id);
|
||||
let positioned = container(panel)
|
||||
.align_left(Fill)
|
||||
@@ -891,7 +904,7 @@ impl Ribbon {
|
||||
})
|
||||
.width(Length::Fixed(200.0));
|
||||
|
||||
let top_offset = self.tab_bar_height() + TOOL_BAR_H;
|
||||
let top_offset = self.tab_bar_height() + self.tool_bar_height();
|
||||
let groups = self.modules.get(self.active)?.ribbon_groups();
|
||||
let left_offset = compute_prop_combo_left(&groups, PROP_COLOR_ID);
|
||||
let positioned = container(panel)
|
||||
@@ -980,7 +993,7 @@ impl Ribbon {
|
||||
})
|
||||
.width(Length::Fixed(220.0));
|
||||
|
||||
let top_offset = self.tab_bar_height() + TOOL_BAR_H;
|
||||
let top_offset = self.tab_bar_height() + self.tool_bar_height();
|
||||
let groups = self.modules.get(self.active)?.ribbon_groups();
|
||||
let left_offset = compute_prop_combo_left(&groups, PROP_LINETYPE_ID);
|
||||
let positioned = container(list)
|
||||
@@ -1056,7 +1069,7 @@ impl Ribbon {
|
||||
})
|
||||
.width(Length::Fixed(width));
|
||||
|
||||
let top_offset = self.tab_bar_height() + TOOL_BAR_H;
|
||||
let top_offset = self.tab_bar_height() + self.tool_bar_height();
|
||||
let groups = self.modules.get(self.active)?.ribbon_groups();
|
||||
let left_offset = compute_prop_combo_left(&groups, dd_id);
|
||||
let positioned = container(panel)
|
||||
@@ -1074,11 +1087,13 @@ impl Ribbon {
|
||||
}
|
||||
}
|
||||
|
||||
/// Render one ribbon tool row for the active module's groups. When `compact`,
|
||||
/// large tools/dropdowns are drawn as small icon columns so the whole row is
|
||||
/// narrower; the combo groups (layer / properties / style) always stay full.
|
||||
/// Render the active module's ribbon panels as a list of group elements (with
|
||||
/// 1px dividers between them). When `compact`, large tools/dropdowns are drawn
|
||||
/// as small icon columns so each panel is narrower; the combo groups (layer /
|
||||
/// properties / style) always stay full. Each panel is a fixed `TOOL_BAR_H`
|
||||
/// tall so the list can be laid out in a row or flex-wrapped across rows.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn build_tool_row<'a>(
|
||||
fn build_tool_groups<'a>(
|
||||
compact: bool,
|
||||
groups: &[RibbonGroup],
|
||||
active_tool: &Option<String>,
|
||||
@@ -1092,7 +1107,7 @@ fn build_tool_row<'a>(
|
||||
active_linetype: &'a str,
|
||||
active_lineweight: LineWeight,
|
||||
style_ctx: &StyleContext,
|
||||
) -> Element<'a, Message> {
|
||||
) -> Vec<Element<'a, Message>> {
|
||||
let mut widgets: Vec<Element<Message>> = Vec::new();
|
||||
let mut first_group = true;
|
||||
|
||||
@@ -1101,7 +1116,7 @@ fn build_tool_row<'a>(
|
||||
widgets.push(
|
||||
container(text(""))
|
||||
.width(1)
|
||||
.height(Fill)
|
||||
.height(Length::Fixed(TOOL_BAR_H))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BORDER_DARK)),
|
||||
..Default::default()
|
||||
@@ -1169,15 +1184,12 @@ fn build_tool_row<'a>(
|
||||
.align_x(iced::Center)
|
||||
.spacing(0)
|
||||
.padding([3u16, 4])
|
||||
.height(Length::Fill)
|
||||
.height(Length::Fixed(TOOL_BAR_H))
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
widgets
|
||||
.into_iter()
|
||||
.fold(row![].spacing(0).height(Length::Fill), |r, w| r.push(w))
|
||||
.into()
|
||||
}
|
||||
|
||||
impl Default for Ribbon {
|
||||
|
||||
+15
-3
@@ -621,6 +621,7 @@ impl<'a> From<WrapFlow<'a>> for Element<'a, Message> {
|
||||
pub struct DensitySwap<'a> {
|
||||
variants: Vec<Element<'a, Message>>,
|
||||
chosen: Cell<usize>,
|
||||
height_out: Option<Arc<AtomicU32>>,
|
||||
}
|
||||
|
||||
impl<'a> DensitySwap<'a> {
|
||||
@@ -628,8 +629,16 @@ impl<'a> DensitySwap<'a> {
|
||||
Self {
|
||||
variants,
|
||||
chosen: Cell::new(0),
|
||||
height_out: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Report the chosen variant's height (bits of an `f32`) so callers can
|
||||
/// anchor overlays below a possibly-taller (wrapped) tool area.
|
||||
pub fn report_height(mut self, out: Arc<AtomicU32>) -> Self {
|
||||
self.height_out = Some(out);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget<Message, Theme, Renderer> for DensitySwap<'a> {
|
||||
@@ -643,7 +652,7 @@ impl<'a> Widget<Message, Theme, Renderer> for DensitySwap<'a> {
|
||||
}
|
||||
|
||||
fn size(&self) -> Size<Length> {
|
||||
Size::new(Length::Shrink, Length::Fill)
|
||||
Size::new(Length::Shrink, Length::Shrink)
|
||||
}
|
||||
|
||||
fn layout(
|
||||
@@ -654,10 +663,10 @@ impl<'a> Widget<Message, Theme, Renderer> for DensitySwap<'a> {
|
||||
) -> layout::Node {
|
||||
let max_w = limits.max().width;
|
||||
let natural =
|
||||
layout::Limits::new(Size::ZERO, Size::new(f32::INFINITY, limits.max().height));
|
||||
layout::Limits::new(Size::ZERO, Size::new(f32::INFINITY, f32::INFINITY));
|
||||
|
||||
// Widest-first: keep the first variant whose natural width fits; else the
|
||||
// last (narrowest).
|
||||
// last (which is expected to wrap to fit any width).
|
||||
let mut pick = self.variants.len().saturating_sub(1);
|
||||
for (i, v) in self.variants.iter_mut().enumerate() {
|
||||
let n = v.as_widget_mut().layout(&mut tree.children[i], renderer, &natural);
|
||||
@@ -673,6 +682,9 @@ impl<'a> Widget<Message, Theme, Renderer> for DensitySwap<'a> {
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[pick], renderer, limits);
|
||||
let sz = node.size();
|
||||
if let Some(out) = &self.height_out {
|
||||
out.store(sz.height.to_bits(), Ordering::Relaxed);
|
||||
}
|
||||
layout::Node::with_children(sz, vec![node])
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user