Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ per crate.

## Unreleased

- Popups: `Flyout::new` now takes the trigger's window-space `Bounds` and anchors with window snapping (breaking, no in-repo callers); popup motion offsets with `top` instead of a margin; `fluent_core::layer` holds the shared deferred-priority table; menu rows wait 200 ms before closing an open submenu in favour of a sibling row (WI-3255).
- Pill, badge, dock, app-dot and date-time action radii and the tree/list badge font size now come from theme tokens; avatar and app-dot initials use `on_accent` (WI-3266).

- Pressed (active) backgrounds on toggle button, checkbox, radio, chip, tabs, tree, list, toolbar, menus, dialogs, dock, ribbon and title bar; switch knob grows on hover/press; command palette disabled rows drop the opacity hack (WI-3265).

- New `list_item_height` component token (32px, Fluent 2); tree and data table rows use it instead of `menu_item_height` (WI-3259).

- Text input error state and selection, and title-bar close hover, now use theme colours (WI-3264, WI-3266)
Expand Down
5 changes: 5 additions & 0 deletions crates/fluent-app/src/title_bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ impl Render for TitleBar {
let fg = colors.on_neutral;
let ctrl_hover = colors.subtle_hover;
let close_hover = colors.status_error;
let ctrl_pressed = colors.subtle_selected;
let close_pressed = colors.status_error.opacity(0.8);

// Drag: left-button press starts window move; consecutive presses
// within DOUBLE_CLICK_WINDOW_MS toggle maximize / restore instead.
Expand Down Expand Up @@ -189,6 +191,7 @@ impl Render for TitleBar {
.justify_center()
.cursor_pointer()
.hover(move |s| s.bg(ctrl_hover))
.active(move |s| s.bg(ctrl_pressed))
.on_click(min_handler)
.child(
svg()
Expand All @@ -207,6 +210,7 @@ impl Render for TitleBar {
.justify_center()
.cursor_pointer()
.hover(move |s| s.bg(ctrl_hover))
.active(move |s| s.bg(ctrl_pressed))
.on_click(max_handler)
.child(
svg()
Expand All @@ -225,6 +229,7 @@ impl Render for TitleBar {
.justify_center()
.cursor_pointer()
.hover(move |s| s.bg(close_hover))
.active(move |s| s.bg(close_pressed))
.on_click(close_handler)
.child(
svg()
Expand Down
21 changes: 21 additions & 0 deletions crates/fluent-core/src/layer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//! Paint order for deferred popups.
//!
//! `deferred(..).with_priority(n)` paints higher priorities on top. Every popup
//! picks its priority from this table so independent popups stack the same way
//! wherever they are opened: tooltip > context menu > submenu > menu >
//! dropdown / popover. A popup nested in another uses the parent's priority
//! plus its depth (`SUBMENU + depth`, `CONTEXT_MENU + depth`), which stays
//! inside its tier because the tiers are spaced well apart.

/// Popovers, suggestion lists and anything else with no stronger claim.
pub const POPOVER: usize = 0;
/// Dropdown, combobox and date/time picker lists.
pub const DROPDOWN: usize = 0;
/// Menu-button and menu-bar menus, and `Flyout` by default.
pub const MENU: usize = 10;
/// Submenus of menu-button and menu-bar menus; add the nesting depth.
pub const SUBMENU: usize = 20;
/// Pointer-positioned context menus and their submenus; add the nesting depth.
pub const CONTEXT_MENU: usize = 40;
/// Tooltips paint over every other popup.
pub const TOOLTIP: usize = 80;
1 change: 1 addition & 0 deletions crates/fluent-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
mod async_resource;
mod colors;
mod components;
pub mod layer;
mod motion;
mod radii;
mod shadows;
Expand Down
5 changes: 4 additions & 1 deletion crates/fluent-core/src/motion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,10 @@ pub fn popup_motion_surface(
} else {
offset * (1.0 - delta)
};
surface.opacity(opacity).mt(px(y_offset))
// Offset with `top`, not a margin: `anchored` ignores margins when it
// measures and snaps its child, so a margin would paint outside the
// snapped bounds for the length of the animation.
surface.opacity(opacity).relative().top(px(y_offset))
},
)
.into_any_element()
Expand Down
1 change: 0 additions & 1 deletion crates/fluent-layout/src/command_palette.rs
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,6 @@ impl Render for CommandPalette {
.px(px(spacing.md))
.py(px(spacing.sm))
.bg(row_bg)
.when(disabled, |d| d.opacity(0.72))
.when(!disabled, |d| {
d.cursor_pointer()
.hover(move |s| s.bg(row_hover))
Expand Down
93 changes: 65 additions & 28 deletions crates/fluent-layout/src/context_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ pub struct ContextMenu {
focus_handle: Option<FocusHandle>,
open_path: Vec<usize>,
selected_path: Vec<usize>,
hover_intent: menu_tree::HoverIntent,
}

impl ContextMenu {
Expand All @@ -292,6 +293,7 @@ impl ContextMenu {
focus_handle: None,
open_path: Vec::new(),
selected_path: Vec::new(),
hover_intent: menu_tree::HoverIntent::default(),
}
}

Expand Down Expand Up @@ -542,18 +544,22 @@ impl ContextMenu {
if !disabled && !self.closing {
row = row
.on_mouse_move(cx.listener(move |menu: &mut ContextMenu, _, _, cx| {
if menu.closing {
return;
}
menu.selected_path = select_path.clone();
menu.open_path = hover_path.clone();
cx.notify();
menu_tree::hover_row(
menu,
select_path.clone(),
hover_path.clone(),
cx,
);
}))
.cursor_pointer()
.hover({
let hover = env.colors.subtle_hover;
move |s| s.bg(hover)
})
.active({
let pressed = env.colors.subtle_selected;
move |s| s.bg(pressed)
})
.on_mouse_down(MouseButton::Left, move |_, win, cx| {
if closer.update(cx, |menu, _| menu.closing).unwrap_or(true) {
return;
Expand Down Expand Up @@ -607,18 +613,22 @@ impl ContextMenu {
if !disabled && !self.closing {
row = row
.on_mouse_move(cx.listener(move |menu: &mut ContextMenu, _, _, cx| {
if menu.closing {
return;
}
menu.selected_path = select_path.clone();
menu.open_path = hover_path.clone();
cx.notify();
menu_tree::hover_row(
menu,
select_path.clone(),
hover_path.clone(),
cx,
);
}))
.cursor_pointer()
.hover({
let hover = env.colors.subtle_hover;
move |s| s.bg(hover)
})
.active({
let pressed = env.colors.subtle_selected;
move |s| s.bg(pressed)
})
.on_mouse_down(MouseButton::Left, move |_, win, cx| {
if closer.update(cx, |menu, _| menu.closing).unwrap_or(true) {
return;
Expand Down Expand Up @@ -678,18 +688,22 @@ impl ContextMenu {
if !disabled && !self.closing {
row = row
.on_mouse_move(cx.listener(move |menu: &mut ContextMenu, _, _, cx| {
if menu.closing {
return;
}
menu.selected_path = select_path.clone();
menu.open_path = hover_path.clone();
cx.notify();
menu_tree::hover_row(
menu,
select_path.clone(),
hover_path.clone(),
cx,
);
}))
.cursor_pointer()
.hover({
let hover = env.colors.subtle_hover;
move |s| s.bg(hover)
})
.active({
let pressed = env.colors.subtle_selected;
move |s| s.bg(pressed)
})
.on_mouse_down(MouseButton::Left, move |_, win, cx| {
if closer.update(cx, |menu, _| menu.closing).unwrap_or(true) {
return;
Expand Down Expand Up @@ -747,13 +761,17 @@ impl ContextMenu {
let hover = env.colors.subtle_hover;
move |s| s.bg(hover)
})
.active({
let pressed = env.colors.subtle_selected;
move |s| s.bg(pressed)
})
.on_mouse_move(cx.listener(move |menu: &mut ContextMenu, _, _, cx| {
if menu.closing {
return;
}
menu.selected_path = select_path.clone();
menu.open_path = select_path.clone();
cx.notify();
menu_tree::hover_row(
menu,
select_path.clone(),
select_path.clone(),
cx,
);
}));
}

Expand All @@ -769,7 +787,11 @@ impl ContextMenu {
if !disabled && menu_tree::path_starts_with(&self.open_path, &path) {
let submenu =
self.render_menu(items, path.clone(), self_weak.clone(), env, cx);
row = row.child(menu_tree::submenu_flyout(submenu, env, path.len()));
row = row.child(menu_tree::submenu_flyout(
submenu,
env,
fluent_core::layer::CONTEXT_MENU + path.len(),
));
}
menu = menu.child(row);
}
Expand All @@ -789,6 +811,20 @@ impl ContextMenu {
}
}

impl menu_tree::HoverMenu for ContextMenu {
fn is_closing(&self) -> bool {
self.closing
}

fn hover_intent(&mut self) -> &mut menu_tree::HoverIntent {
&mut self.hover_intent
}

fn paths_mut(&mut self) -> (&mut Vec<usize>, &mut Vec<usize>) {
(&mut self.selected_path, &mut self.open_path)
}
}

impl Render for ContextMenu {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
if !self.observing_theme {
Expand Down Expand Up @@ -842,9 +878,10 @@ impl Render for ContextMenu {
menu.handle_key(ev, window, cx);
}),
)
.child(deferred(
overlay.child(anchored().position(pos).child(menu)),
))
.child(
deferred(overlay.child(anchored().position(pos).child(menu)))
.with_priority(fluent_core::layer::CONTEXT_MENU),
)
}
}

Expand Down
10 changes: 10 additions & 0 deletions crates/fluent-layout/src/dialog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ impl RenderOnce for Dialog {
} else {
colors.neutral_hover
};
let pressed = if action.primary {
colors.accent_selected
} else {
colors.neutral_selected
};
let fg = if action.primary {
colors.on_accent
} else {
Expand All @@ -144,6 +149,7 @@ impl RenderOnce for Dialog {
.text_color(fg)
.cursor_pointer()
.hover(move |s| s.bg(hover))
.active(move |s| s.bg(pressed))
.on_click(move |ev, win, app| handler(ev, win, app))
.child(action.label),
);
Expand Down Expand Up @@ -180,6 +186,10 @@ impl RenderOnce for Dialog {
let hover = colors.subtle_hover;
move |s| s.bg(hover)
})
.active({
let pressed = colors.subtle_selected;
move |s| s.bg(pressed)
})
.on_click(|_, _, cx| ModalStack::pop(cx))
.child(
svg()
Expand Down
4 changes: 3 additions & 1 deletion crates/fluent-layout/src/dock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ impl Render for DockPanel {
let panel_border = colors.panel_border;
let on_neutral = colors.on_neutral;
let hover_bg = colors.subtle_hover;
let pressed_bg = colors.subtle_selected;
let handle_hover = colors.stroke_neutral_dim;

let title = self.title.clone();
Expand Down Expand Up @@ -161,8 +162,9 @@ impl Render for DockPanel {
.items_center()
.justify_center()
.cursor_pointer()
.rounded(px(2.0))
.rounded(px(theme.radii.sm))
.hover(move |s| s.bg(hover_bg))
.active(move |s| s.bg(pressed_bg))
.on_click(collapse_handler)
.child(
svg()
Expand Down
Loading
Loading