feat(ui): MTEXT editor in shared modal + resizable dialogs

Route the MTEXT editor through the shared in-canvas modal frame with an
Apply-at-top toolbar (style-manager style): Apply commits and keeps the
editor open (creates on first apply, updates in place after), the ✕
cancels. Give every modal a bottom-right corner resize grip driven by a
shared grow-only `modal_resize` delta that resets with `modal_offset` on
open/close, so each dialog opens at its natural size and can be dragged
larger — style managers included.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-07-19 15:47:09 +03:00
1 parent f797fd800b
commit 977701b47e
10 files changed
+221 -70

No files matched your search

+1
View File
@@ -53,6 +53,7 @@ pub const TRASH: &[u8] = include_bytes!("../../assets/icons/ui/trash.svg");
pub const COPY: &[u8] = include_bytes!("../../assets/icons/ui/copy.svg");
pub const MENU: &[u8] = include_bytes!("../../assets/icons/ui/menu.svg");
pub const MOVE: &[u8] = include_bytes!("../../assets/icons/ui/move.svg");
pub const RESIZE: &[u8] = include_bytes!("../../assets/icons/ui/resize.svg");
pub const SPLIT_V: &[u8] = include_bytes!("../../assets/icons/ui/split_v.svg");
pub const SPLIT_H: &[u8] = include_bytes!("../../assets/icons/ui/split_h.svg");
pub const GRID: &[u8] = include_bytes!("../../assets/icons/ui/grid.svg");
+31 -15
View File
@@ -48,6 +48,7 @@ pub fn modal<'a>(
content: impl Into<Element<'a, Message>>,
on_close: Message,
offset: Vector,
resizable: bool,
) -> Element<'a, Message> {
let close = button(crate::ui::icons::tinted(
crate::ui::icons::CLOSE,
@@ -84,23 +85,38 @@ pub fn modal<'a>(
let title_bar = row![grip, close].spacing(6).align_y(iced::Center);
// The column shrinks to the content's width; the title bar sits top-right
// above it, mirroring the former ✕ placement with a drag handle.
let framed = container(
let panel_style = |_: &Theme| container::Style {
background: Some(Background::Color(PANEL)),
border: Border {
color: BORDER_C,
width: 1.0,
radius: 6.0.into(),
},
..Default::default()
};
// The dialog is always sized to its content (the caller fixes the content's
// width/height, growing it by the shared resize delta). When `resizable`, a
// corner grip is appended bottom-right; dragging it drives `ModalResizeGrab`
// + the shared drag move/release, which bumps that delta. The title bar sits
// top-right above the content either way (`align_x(Right)`).
let body = if resizable {
let resize = mouse_area(
container(crate::ui::icons::tinted(crate::ui::icons::RESIZE, 15.0, GRIP_C))
.padding([0, 2]),
)
.on_press(Message::ModalResizeGrab)
.interaction(iced::mouse::Interaction::Grab);
column![title_bar, content.into(), resize]
} else {
column![title_bar, content.into()]
.spacing(6)
.align_x(iced::alignment::Horizontal::Right),
};
let framed: Element<'a, Message> = container(
body.spacing(6).align_x(iced::alignment::Horizontal::Right),
)
.padding(10)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(PANEL)),
border: Border {
color: BORDER_C,
width: 1.0,
radius: 6.0.into(),
},
..Default::default()
});
.padding(10)
.style(panel_style)
.into();
// Position via asymmetric padding (padding is non-negative): shifting a
// centred box by `d` on an axis needs (near − far) padding = 2·d there.