refactor(ui): adopt iced layout widgets
This commit is contained in:
parent
c9c3d3b1bf
commit
5116369dec
6 changed files with 123 additions and 266 deletions
|
|
@ -52,7 +52,7 @@ iced = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00
|
||||||
iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" }
|
iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" }
|
||||||
# iced_aw 0.15 compatibility is pending upstream in PR #432. Pin the exact
|
# iced_aw 0.15 compatibility is pending upstream in PR #432. Pin the exact
|
||||||
# reviewed pair of commits so Iced and its additional widgets cannot drift.
|
# reviewed pair of commits so Iced and its additional widgets cannot drift.
|
||||||
iced_aw = { git = "https://github.com/tsuza/iced_aw.git", rev = "a9301708a4d008dc17f63cbbbb2b2aa06e0e7352", features = ["menu", "context_menu", "color_picker"] }
|
iced_aw = { git = "https://github.com/tsuza/iced_aw.git", rev = "a9301708a4d008dc17f63cbbbb2b2aa06e0e7352", features = ["menu", "context_menu", "color_picker", "drop_down"] }
|
||||||
bytemuck = { version = "1.25", features = ["derive"] }
|
bytemuck = { version = "1.25", features = ["derive"] }
|
||||||
glam = { version = "0.33", features = ["bytemuck"] }
|
glam = { version = "0.33", features = ["bytemuck"] }
|
||||||
truck-modeling = "0.6"
|
truck-modeling = "0.6"
|
||||||
|
|
|
||||||
|
|
@ -150,24 +150,20 @@ impl OpenCADStudio {
|
||||||
// viewport draws the layout's own geometry (white sheet + entities +
|
// viewport draws the layout's own geometry (white sheet + entities +
|
||||||
// borders) and the floating content viewports blit on top.
|
// borders) and the floating content viewports blit on top.
|
||||||
let viewport_3d: Element<'_, Message> = if tab.is_start {
|
let viewport_3d: Element<'_, Message> = if tab.is_start {
|
||||||
responsive(|size| {
|
start_page_view(
|
||||||
start_page_view(
|
&self.patrons,
|
||||||
&self.patrons,
|
&self.videos,
|
||||||
&self.videos,
|
self.videos_loading,
|
||||||
self.videos_loading,
|
&self.video_thumbs,
|
||||||
&self.video_thumbs,
|
&self.discussions,
|
||||||
&self.discussions,
|
self.discussions_loading,
|
||||||
self.discussions_loading,
|
&self.recent_files,
|
||||||
&self.recent_files,
|
&self.recent_thumbs,
|
||||||
&self.recent_thumbs,
|
self.recent_limit,
|
||||||
self.recent_limit,
|
&self.recent_limit_input,
|
||||||
&self.recent_limit_input,
|
self.start_action_w.clone(),
|
||||||
size.width,
|
self.start_section,
|
||||||
self.start_action_w.clone(),
|
)
|
||||||
self.start_section,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.into()
|
|
||||||
} else if is_paper {
|
} else if is_paper {
|
||||||
shader(ViewportPane::model(
|
shader(ViewportPane::model(
|
||||||
&tab.scene,
|
&tab.scene,
|
||||||
|
|
@ -2124,7 +2120,13 @@ pub(super) fn doc_tab_bar<'a>(tabs: &'a [DocumentTab], active_tab: usize) -> Ele
|
||||||
|
|
||||||
items.push(new_btn.into());
|
items.push(new_btn.into());
|
||||||
|
|
||||||
container(WrapFlow::new(items).spacing_x(0.0).row_h(30.0))
|
container(
|
||||||
|
Row::with_children(items)
|
||||||
|
.spacing(0.0)
|
||||||
|
.align_y(iced::Center)
|
||||||
|
.wrap()
|
||||||
|
.vertical_spacing(0.0),
|
||||||
|
)
|
||||||
.style(|theme: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(
|
background: Some(Background::Color(
|
||||||
theme.palette().background.base.color,
|
theme.palette().background.base.color,
|
||||||
|
|
@ -2254,6 +2256,43 @@ pub(super) fn collapse_bar<'a>(name: &str, on_press: Message) -> Element<'a, Mes
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn start_page_view<'a>(
|
pub(super) fn start_page_view<'a>(
|
||||||
|
patrons: &'a [(String, i64)],
|
||||||
|
videos: &'a [crate::videos::VideoEntry],
|
||||||
|
videos_loading: bool,
|
||||||
|
video_thumbs: &'a std::collections::HashMap<String, iced::widget::image::Handle>,
|
||||||
|
discussions: &'a [crate::discussions::DiscussionEntry],
|
||||||
|
discussions_loading: bool,
|
||||||
|
recents: &'a [std::path::PathBuf],
|
||||||
|
thumbs: &'a std::collections::HashMap<
|
||||||
|
std::path::PathBuf,
|
||||||
|
Option<iced::widget::image::Handle>,
|
||||||
|
>,
|
||||||
|
recent_limit: usize,
|
||||||
|
recent_limit_input: &'a str,
|
||||||
|
action_width_out: std::sync::Arc<std::sync::atomic::AtomicU32>,
|
||||||
|
active: super::StartSection,
|
||||||
|
) -> Element<'a, Message> {
|
||||||
|
responsive(move |size| {
|
||||||
|
start_page_content(
|
||||||
|
patrons,
|
||||||
|
videos,
|
||||||
|
videos_loading,
|
||||||
|
video_thumbs,
|
||||||
|
discussions,
|
||||||
|
discussions_loading,
|
||||||
|
recents,
|
||||||
|
thumbs,
|
||||||
|
recent_limit,
|
||||||
|
recent_limit_input,
|
||||||
|
size.width,
|
||||||
|
action_width_out.clone(),
|
||||||
|
active,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_page_content<'a>(
|
||||||
patrons: &'a [(String, i64)],
|
patrons: &'a [(String, i64)],
|
||||||
videos: &'a [crate::videos::VideoEntry],
|
videos: &'a [crate::videos::VideoEntry],
|
||||||
videos_loading: bool,
|
videos_loading: bool,
|
||||||
|
|
@ -2819,15 +2858,17 @@ pub(super) fn start_page_view<'a>(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
let tab_bar = WrapFlow::new(vec![
|
let tab_bar = Row::with_children(vec![
|
||||||
tab_btn("Recent Files", super::StartSection::Recent).into(),
|
tab_btn("Recent Files", super::StartSection::Recent).into(),
|
||||||
tab_btn("Videos", super::StartSection::Videos).into(),
|
tab_btn("Videos", super::StartSection::Videos).into(),
|
||||||
tab_btn("Welcome", super::StartSection::Welcome).into(),
|
tab_btn("Welcome", super::StartSection::Welcome).into(),
|
||||||
tab_btn("Discussions", super::StartSection::Discussions).into(),
|
tab_btn("Discussions", super::StartSection::Discussions).into(),
|
||||||
tab_btn("Supporters", super::StartSection::Supporters).into(),
|
tab_btn("Supporters", super::StartSection::Supporters).into(),
|
||||||
])
|
])
|
||||||
.spacing_x(6.0)
|
.spacing(6.0)
|
||||||
.row_h(38.0);
|
.align_y(iced::Center)
|
||||||
|
.wrap()
|
||||||
|
.vertical_spacing(0.0);
|
||||||
let section_body: Element<'a, Message> = match active {
|
let section_body: Element<'a, Message> = match active {
|
||||||
super::StartSection::Recent => container(recent)
|
super::StartSection::Recent => container(recent)
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,8 @@
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
use crate::ui::properties::acad_color_display;
|
use crate::ui::properties::acad_color_display;
|
||||||
use acadrust::types::Color as AcadColor;
|
use acadrust::types::Color as AcadColor;
|
||||||
use iced::advanced::layout::{self, Layout};
|
|
||||||
use iced::advanced::widget::{self, Widget};
|
|
||||||
use iced::advanced::{mouse, overlay, renderer, Shell};
|
|
||||||
use iced::widget::{button, column, container, row, text};
|
use iced::widget::{button, column, container, row, text};
|
||||||
use iced::{Background, Border, Color, Element, Event, Length, Point, Rectangle, Renderer, Size, Theme, Vector};
|
use iced::{Background, Border, Color, Element, Length, Theme};
|
||||||
|
|
||||||
/// Which "logical" entries the colour list offers besides the standard ACI
|
/// Which "logical" entries the colour list offers besides the standard ACI
|
||||||
/// colours.
|
/// colours.
|
||||||
|
|
@ -127,6 +124,7 @@ pub fn color_selector<'a>(
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
let (cur_bg, _) = acad_color_display(current);
|
let (cur_bg, _) = acad_color_display(current);
|
||||||
let cur_name = color_display_name(current);
|
let cur_name = color_display_name(current);
|
||||||
|
let on_dismiss = on_toggle.clone();
|
||||||
|
|
||||||
// Closed button: current swatch + name + caret.
|
// Closed button: current swatch + name + caret.
|
||||||
let head = button(
|
let head = button(
|
||||||
|
|
@ -162,12 +160,14 @@ pub fn color_selector<'a>(
|
||||||
.padding(5)
|
.padding(5)
|
||||||
.width(220);
|
.width(220);
|
||||||
|
|
||||||
// The popup is shown as a floating overlay (anchored below the button) so
|
// `DropDown` keeps the popup outside the surrounding form layout and
|
||||||
// it doesn't push the surrounding form down.
|
// handles viewport placement, Escape, and outside-click dismissal.
|
||||||
Element::new(Floating {
|
iced_aw::DropDown::new(head, popup, true)
|
||||||
base: head.into(),
|
.width(220)
|
||||||
popup: popup.into(),
|
.alignment(iced_aw::drop_down::Alignment::Bottom)
|
||||||
})
|
.offset(2.0)
|
||||||
|
.on_dismiss(on_dismiss)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_row_style(theme: &Theme, status: button::Status) -> button::Style {
|
fn list_row_style(theme: &Theme, status: button::Status) -> button::Style {
|
||||||
|
|
@ -227,224 +227,19 @@ pub fn color_list<'a>(
|
||||||
list.into()
|
list.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render `base` inline with `popup` floating just below it — the shared
|
/// Render `base` inline with `popup` in an `iced_aw` dropdown.
|
||||||
/// dropdown mechanic for the panel's custom dropdowns (colour picker, block
|
pub fn drop_down_below<'a>(
|
||||||
/// Name). Unlike iced's menu overlay it always opens downward.
|
|
||||||
pub fn floating_below<'a>(
|
|
||||||
base: Element<'a, Message>,
|
base: Element<'a, Message>,
|
||||||
popup: Element<'a, Message>,
|
popup: Element<'a, Message>,
|
||||||
|
popup_width: Length,
|
||||||
|
popup_height: Length,
|
||||||
|
on_dismiss: Message,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
Element::new(Floating { base, popup })
|
iced_aw::DropDown::new(base, popup, true)
|
||||||
}
|
.width(popup_width)
|
||||||
|
.height(popup_height)
|
||||||
/// A widget that renders `base` inline and `popup` as a floating overlay
|
.alignment(iced_aw::drop_down::Alignment::Bottom)
|
||||||
/// anchored just below it.
|
.offset(2.0)
|
||||||
struct Floating<'a> {
|
.on_dismiss(on_dismiss)
|
||||||
base: Element<'a, Message>,
|
.into()
|
||||||
popup: Element<'a, Message>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Widget<Message, Theme, Renderer> for Floating<'a> {
|
|
||||||
fn diff(&mut self, tree: &mut widget::Tree) {
|
|
||||||
tree.diff_children(&mut [&mut self.base, &mut self.popup]);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn size(&self) -> Size<Length> {
|
|
||||||
self.base.as_widget().size()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn layout(
|
|
||||||
&mut self,
|
|
||||||
tree: &mut widget::Tree,
|
|
||||||
renderer: &Renderer,
|
|
||||||
limits: &layout::Limits,
|
|
||||||
) -> layout::Node {
|
|
||||||
self.base
|
|
||||||
.as_widget_mut()
|
|
||||||
.layout(&mut tree.children[0], renderer, limits)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(
|
|
||||||
&mut self,
|
|
||||||
tree: &mut widget::Tree,
|
|
||||||
event: &Event,
|
|
||||||
layout: Layout<'_>,
|
|
||||||
cursor: mouse::Cursor,
|
|
||||||
renderer: &Renderer,
|
|
||||||
shell: &mut Shell<'_, Message>,
|
|
||||||
viewport: &Rectangle,
|
|
||||||
) {
|
|
||||||
self.base.as_widget_mut().update(
|
|
||||||
&mut tree.children[0],
|
|
||||||
event,
|
|
||||||
layout,
|
|
||||||
cursor,
|
|
||||||
renderer,
|
|
||||||
shell,
|
|
||||||
viewport,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mouse_interaction(
|
|
||||||
&self,
|
|
||||||
tree: &widget::Tree,
|
|
||||||
layout: Layout<'_>,
|
|
||||||
cursor: mouse::Cursor,
|
|
||||||
viewport: &Rectangle,
|
|
||||||
renderer: &Renderer,
|
|
||||||
) -> mouse::Interaction {
|
|
||||||
self.base.as_widget().mouse_interaction(
|
|
||||||
&tree.children[0],
|
|
||||||
layout,
|
|
||||||
cursor,
|
|
||||||
viewport,
|
|
||||||
renderer,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn operate(
|
|
||||||
&mut self,
|
|
||||||
tree: &mut widget::Tree,
|
|
||||||
layout: Layout<'_>,
|
|
||||||
renderer: &Renderer,
|
|
||||||
operation: &mut dyn widget::Operation,
|
|
||||||
) {
|
|
||||||
self.base
|
|
||||||
.as_widget_mut()
|
|
||||||
.operate(&mut tree.children[0], layout, renderer, operation);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw(
|
|
||||||
&self,
|
|
||||||
tree: &widget::Tree,
|
|
||||||
renderer: &mut Renderer,
|
|
||||||
theme: &Theme,
|
|
||||||
style: &renderer::Style,
|
|
||||||
layout: Layout<'_>,
|
|
||||||
cursor: mouse::Cursor,
|
|
||||||
viewport: &Rectangle,
|
|
||||||
) {
|
|
||||||
self.base.as_widget().draw(
|
|
||||||
&tree.children[0],
|
|
||||||
renderer,
|
|
||||||
theme,
|
|
||||||
style,
|
|
||||||
layout,
|
|
||||||
cursor,
|
|
||||||
viewport,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn overlay<'b>(
|
|
||||||
&'b mut self,
|
|
||||||
tree: &'b mut widget::Tree,
|
|
||||||
layout: Layout<'b>,
|
|
||||||
_renderer: &Renderer,
|
|
||||||
_viewport: &Rectangle,
|
|
||||||
translation: Vector,
|
|
||||||
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
|
||||||
let bounds = layout.bounds();
|
|
||||||
let anchor = Point::new(
|
|
||||||
bounds.x + translation.x,
|
|
||||||
bounds.y + bounds.height + translation.y + 2.0,
|
|
||||||
);
|
|
||||||
Some(overlay::Element::new(Box::new(FloatingOverlay {
|
|
||||||
popup: &mut self.popup,
|
|
||||||
tree: &mut tree.children[1],
|
|
||||||
anchor,
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<Floating<'a>> for Element<'a, Message> {
|
|
||||||
fn from(f: Floating<'a>) -> Self {
|
|
||||||
Element::new(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FloatingOverlay<'a, 'b> {
|
|
||||||
popup: &'b mut Element<'a, Message>,
|
|
||||||
tree: &'b mut widget::Tree,
|
|
||||||
anchor: Point,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl overlay::Overlay<Message, Theme, Renderer> for FloatingOverlay<'_, '_> {
|
|
||||||
fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node {
|
|
||||||
let viewport = Rectangle::with_size(bounds);
|
|
||||||
let limits = layout::Limits::new(Size::ZERO, viewport.size());
|
|
||||||
let node = self
|
|
||||||
.popup
|
|
||||||
.as_widget_mut()
|
|
||||||
.layout(self.tree, renderer, &limits);
|
|
||||||
let size = node.size();
|
|
||||||
let mut x = self.anchor.x;
|
|
||||||
let mut y = self.anchor.y;
|
|
||||||
if x + size.width > viewport.width {
|
|
||||||
x = (viewport.width - size.width).max(0.0);
|
|
||||||
}
|
|
||||||
if y + size.height > viewport.height {
|
|
||||||
// Not enough room below — flip above the anchor.
|
|
||||||
y = (self.anchor.y - bounds.height.min(0.0) - size.height).max(0.0);
|
|
||||||
}
|
|
||||||
layout::Node::with_children(size, vec![node]).translate(Vector::new(x, y))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw(
|
|
||||||
&self,
|
|
||||||
renderer: &mut Renderer,
|
|
||||||
theme: &Theme,
|
|
||||||
style: &renderer::Style,
|
|
||||||
layout: Layout<'_>,
|
|
||||||
cursor: mouse::Cursor,
|
|
||||||
) {
|
|
||||||
let child = layout.children().next().unwrap();
|
|
||||||
self.popup.as_widget().draw(
|
|
||||||
self.tree,
|
|
||||||
renderer,
|
|
||||||
theme,
|
|
||||||
style,
|
|
||||||
child,
|
|
||||||
cursor,
|
|
||||||
&child.bounds(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(
|
|
||||||
&mut self,
|
|
||||||
event: &Event,
|
|
||||||
layout: Layout<'_>,
|
|
||||||
cursor: mouse::Cursor,
|
|
||||||
renderer: &Renderer,
|
|
||||||
shell: &mut Shell<'_, Message>,
|
|
||||||
) {
|
|
||||||
let child = layout.children().next().unwrap();
|
|
||||||
let vp = child.bounds();
|
|
||||||
self.popup.as_widget_mut().update(
|
|
||||||
self.tree, event, child, cursor, renderer, shell, &vp,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn operate(
|
|
||||||
&mut self,
|
|
||||||
layout: Layout<'_>,
|
|
||||||
renderer: &Renderer,
|
|
||||||
operation: &mut dyn widget::Operation,
|
|
||||||
) {
|
|
||||||
let child = layout.children().next().unwrap();
|
|
||||||
self.popup
|
|
||||||
.as_widget_mut()
|
|
||||||
.operate(self.tree, child, renderer, operation);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mouse_interaction(
|
|
||||||
&self,
|
|
||||||
layout: Layout<'_>,
|
|
||||||
cursor: mouse::Cursor,
|
|
||||||
renderer: &Renderer,
|
|
||||||
) -> mouse::Interaction {
|
|
||||||
let child = layout.children().next().unwrap();
|
|
||||||
self.popup
|
|
||||||
.as_widget()
|
|
||||||
.mouse_interaction(self.tree, child, cursor, &child.bounds(), renderer)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ const COMBO_PAD_V: f32 = (ROW_H - FONT_SZ * 1.3 - 2.0) / 2.0; // fills combo to
|
||||||
const SWATCH_SZ: f32 = ROW_H * 0.54; // ≈14 px color swatch
|
const SWATCH_SZ: f32 = ROW_H * 0.54; // ≈14 px color swatch
|
||||||
const PATTERN_CARD_W: f32 = 158.0;
|
const PATTERN_CARD_W: f32 = 158.0;
|
||||||
const PATTERN_PREVIEW_H: f32 = 58.0;
|
const PATTERN_PREVIEW_H: f32 = 58.0;
|
||||||
|
const PATTERN_PICKER_W: f32 = 348.0;
|
||||||
|
const PATTERN_PICKER_H: f32 = 720.0;
|
||||||
|
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
use crate::scene::model::object::{PropSection, PropValue};
|
use crate::scene::model::object::{PropSection, PropValue};
|
||||||
|
|
@ -862,8 +864,7 @@ impl PropertiesPanel {
|
||||||
/// Editable dropdown row (block reference Name): a text field with a caret
|
/// Editable dropdown row (block reference Name): a text field with a caret
|
||||||
/// button in one bordered control. Typing + Enter commits through the
|
/// button in one bordered control. Typing + Enter commits through the
|
||||||
/// normal PropGeomCommit path (existing name → re-point, new name →
|
/// normal PropGeomCommit path (existing name → re-point, new name →
|
||||||
/// rename); the caret opens a floating list of the definitions (always
|
/// rename); the caret opens a dropdown list of the definitions and picking one
|
||||||
/// downward, via the shared `floating_below` mechanic) and picking one
|
|
||||||
/// applies through PropGeomChoiceChanged. Typed text filters the list.
|
/// applies through PropGeomChoiceChanged. Typed text filters the list.
|
||||||
fn render_edit_choice_row<'a>(
|
fn render_edit_choice_row<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
|
|
@ -966,7 +967,13 @@ impl PropertiesPanel {
|
||||||
|
|
||||||
prop_row_widget(
|
prop_row_widget(
|
||||||
label,
|
label,
|
||||||
crate::ui::color_select::floating_below(head.into(), popup.into()),
|
crate::ui::color_select::drop_down_below(
|
||||||
|
head.into(),
|
||||||
|
popup.into(),
|
||||||
|
Length::Fixed(200.0),
|
||||||
|
Length::Shrink,
|
||||||
|
Message::PropEditChoiceToggle,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1124,19 +1131,25 @@ impl PropertiesPanel {
|
||||||
.into()
|
.into()
|
||||||
} else {
|
} else {
|
||||||
scrollable(grid)
|
scrollable(grid)
|
||||||
.height(Length::Fixed(300.0))
|
.height(Length::Fill)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.into()
|
.into()
|
||||||
};
|
};
|
||||||
let popup = container(column![search, results].spacing(7))
|
let popup = container(column![search, results].spacing(7))
|
||||||
.style(container::bordered_box)
|
.style(container::bordered_box)
|
||||||
.padding(8)
|
.padding(8)
|
||||||
.width(348)
|
.width(PATTERN_PICKER_W)
|
||||||
.height(Length::Fit.max(360.0));
|
.height(Length::Fixed(PATTERN_PICKER_H));
|
||||||
|
|
||||||
prop_row_widget(
|
prop_row_widget(
|
||||||
label,
|
label,
|
||||||
crate::ui::color_select::floating_below(head.into(), popup.into()),
|
crate::ui::color_select::drop_down_below(
|
||||||
|
head.into(),
|
||||||
|
popup.into(),
|
||||||
|
Length::Fixed(PATTERN_PICKER_W),
|
||||||
|
Length::Fixed(PATTERN_PICKER_H),
|
||||||
|
Message::PropHatchPatternPickerToggle(current.to_string()),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -393,7 +393,7 @@ impl Ribbon {
|
||||||
redo_count: usize,
|
redo_count: usize,
|
||||||
) -> Element<'_, Message> {
|
) -> Element<'_, Message> {
|
||||||
// ── Quick-access file commands + undo/redo, one merged flow ────────
|
// ── Quick-access file commands + undo/redo, one merged flow ────────
|
||||||
let lead = WrapFlow::new(vec![
|
let lead = iced::widget::Row::with_children(vec![
|
||||||
quick_access_btn(crate::ui::icons::DOC_NEW, "New", "NEW", is_start).into(),
|
quick_access_btn(crate::ui::icons::DOC_NEW, "New", "NEW", is_start).into(),
|
||||||
quick_access_btn(crate::ui::icons::FOLDER_OPEN, "Open", "OPEN", is_start).into(),
|
quick_access_btn(crate::ui::icons::FOLDER_OPEN, "Open", "OPEN", is_start).into(),
|
||||||
quick_access_btn(crate::ui::icons::SAVE, "Save", "SAVE", is_start).into(),
|
quick_access_btn(crate::ui::icons::SAVE, "Save", "SAVE", is_start).into(),
|
||||||
|
|
@ -402,8 +402,10 @@ impl Ribbon {
|
||||||
render_history_control("Undo", UNDO_HISTORY_ID, undo_count, &self.open_dropdown).into(),
|
render_history_control("Undo", UNDO_HISTORY_ID, undo_count, &self.open_dropdown).into(),
|
||||||
render_history_control("Redo", REDO_HISTORY_ID, redo_count, &self.open_dropdown).into(),
|
render_history_control("Redo", REDO_HISTORY_ID, redo_count, &self.open_dropdown).into(),
|
||||||
])
|
])
|
||||||
.spacing_x(TOP_HIST_GAP)
|
.spacing(TOP_HIST_GAP)
|
||||||
.row_h(28.0);
|
.align_y(iced::Center)
|
||||||
|
.wrap()
|
||||||
|
.vertical_spacing(0.0);
|
||||||
|
|
||||||
// The quick-access flow and the tabs flow each flex-wrap; WrapBar stacks
|
// The quick-access flow and the tabs flow each flex-wrap; WrapBar stacks
|
||||||
// them so a wrapped tab never shares a row with a quick-access button.
|
// them so a wrapped tab never shares a row with a quick-access button.
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ use crate::app::Message;
|
||||||
use crate::snap::Snapper;
|
use crate::snap::Snapper;
|
||||||
use crate::ui::statusbar::statusbar_config::{StatusBarConfig, StatusPill};
|
use crate::ui::statusbar::statusbar_config::{StatusBarConfig, StatusPill};
|
||||||
use crate::ui::statusbar::status_menu::Entry as StatusMenuEntry;
|
use crate::ui::statusbar::status_menu::Entry as StatusMenuEntry;
|
||||||
use crate::ui::wrap_bar::{WrapBar, WrapFlow};
|
use crate::ui::wrap_bar::WrapBar;
|
||||||
|
|
||||||
pub struct StatusMenuData<'a> {
|
pub struct StatusMenuData<'a> {
|
||||||
pub layout_names: Vec<String>,
|
pub layout_names: Vec<String>,
|
||||||
|
|
@ -412,10 +412,12 @@ impl StatusBar {
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
let right_status = WrapFlow::new(pills)
|
let right_status = iced::widget::Row::with_children(pills)
|
||||||
.spacing_x(2.0)
|
.spacing(2.0)
|
||||||
.row_h(30.0)
|
.align_y(iced::Center)
|
||||||
.justify_end(true);
|
.wrap()
|
||||||
|
.vertical_spacing(0.0)
|
||||||
|
.align_x(iced::alignment::Horizontal::Right);
|
||||||
|
|
||||||
// Left area: hamburger menu + Model/layout tabs in a flex-wrap flow, so
|
// Left area: hamburger menu + Model/layout tabs in a flex-wrap flow, so
|
||||||
// they spill onto lower rows when narrow (no scroll arrows). The pills
|
// they spill onto lower rows when narrow (no scroll arrows). The pills
|
||||||
|
|
@ -462,7 +464,11 @@ impl StatusBar {
|
||||||
}
|
}
|
||||||
left.push(add_btn.into());
|
left.push(add_btn.into());
|
||||||
}
|
}
|
||||||
let left_area = WrapFlow::new(left).spacing_x(2.0).row_h(30.0);
|
let left_area = iced::widget::Row::with_children(left)
|
||||||
|
.spacing(2.0)
|
||||||
|
.align_y(iced::Center)
|
||||||
|
.wrap()
|
||||||
|
.vertical_spacing(0.0);
|
||||||
|
|
||||||
let wrap = WrapBar::new(left_area.into(), right_status.into())
|
let wrap = WrapBar::new(left_area.into(), right_status.into())
|
||||||
.min_row_h(30.0)
|
.min_row_h(30.0)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue