fix: capture mouse motion and mouse interactions in overlay

This commit is contained in:
Ashley Wulber 2026-03-05 15:38:28 -05:00 committed by Michael Murphy
parent 1810bedfa5
commit 1970499459
4 changed files with 417 additions and 407 deletions

View file

@ -135,7 +135,7 @@ impl<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static>
self.on_selected.as_ref(), self.on_selected.as_ref(),
self.selections, self.selections,
|| tree.state.downcast_mut::<State<Item>>(), || tree.state.downcast_mut::<State<Item>>(),
) );
} }
fn mouse_interaction( fn mouse_interaction(

View file

@ -585,9 +585,9 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
Cow::Borrowed(_) => panic!(), Cow::Borrowed(_) => panic!(),
Cow::Owned(o) => o.as_mut_slice(), Cow::Owned(o) => o.as_mut_slice(),
}; };
let menu_status = process_menu_events( process_menu_events(
self, self,
&event, event,
view_cursor, view_cursor,
renderer, renderer,
clipboard, clipboard,
@ -629,8 +629,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
if !self.is_overlay && !view_cursor.is_over(viewport) { if !self.is_overlay && !view_cursor.is_over(viewport) {
return None; return None;
} }
let new_root = process_overlay_events(
let (new_root, status) = process_overlay_events(
self, self,
renderer, renderer,
viewport_size, viewport_size,
@ -641,6 +640,10 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
shell, shell,
); );
if self.is_overlay && view_cursor.is_over(viewport) {
shell.capture_event();
}
return new_root; return new_root;
} }
@ -680,24 +683,23 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
feature = "winit", feature = "winit",
feature = "surface-message" feature = "surface-message"
))] ))]
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
if let Some(handler) = self.on_surface_action.as_ref() { && let Some(handler) = self.on_surface_action.as_ref()
let mut root = self.window_id; {
let mut depth = self.depth; let mut root = self.window_id;
while let Some(parent) = let mut depth = self.depth;
state.popup_id.iter().find(|(_, v)| **v == root) while let Some(parent) =
{ state.popup_id.iter().find(|(_, v)| **v == root)
// parent of root popup is the window, so we stop. {
if depth == 0 { // parent of root popup is the window, so we stop.
break; if depth == 0 {
} break;
root = *parent.0;
depth = depth.saturating_sub(1);
} }
shell.publish((handler)(crate::surface::Action::DestroyPopup( root = *parent.0;
root, depth = depth.saturating_sub(1);
)));
} }
shell
.publish((handler)(crate::surface::Action::DestroyPopup(root)));
} }
state.reset(); state.reset();
@ -708,7 +710,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
if self.bar_bounds.contains(overlay_cursor) { if self.bar_bounds.contains(overlay_cursor) {
state.reset(); state.reset();
} }
}) });
} }
_ => {} _ => {}
@ -804,26 +806,25 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
let menu_color = styling.background; let menu_color = styling.background;
r.fill_quad(menu_quad, menu_color); r.fill_quad(menu_quad, menu_color);
// draw path hightlight // draw path hightlight
if let (true, Some(active)) = (draw_path, ms.index) { if let (true, Some(active)) = (draw_path, ms.index)
if let Some(active_layout) = children_layout && let Some(active_layout) = children_layout
.children() .children()
.nth(active.saturating_sub(start_index)) .nth(active.saturating_sub(start_index))
{ {
let path_quad = renderer::Quad { let path_quad = renderer::Quad {
bounds: active_layout bounds: active_layout
.bounds() .bounds()
.intersection(&viewport) .intersection(&viewport)
.unwrap_or_default(), .unwrap_or_default(),
border: Border { border: Border {
radius: styling.menu_border_radius.into(), radius: styling.menu_border_radius.into(),
..Default::default() ..Default::default()
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true, snap: true,
}; };
r.fill_quad(path_quad, styling.path); r.fill_quad(path_quad, styling.path);
}
} }
if start_index < menu_roots.len() { if start_index < menu_roots.len() {
// draw item // draw item
@ -894,6 +895,19 @@ impl<Message: Clone + 'static> overlay::Overlay<Message, crate::Theme, crate::Re
) { ) {
self.draw(renderer, theme, style, layout, cursor); self.draw(renderer, theme, style, layout, cursor);
} }
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor: mouse::Cursor,
_renderer: &crate::Renderer,
) -> mouse::Interaction {
if cursor.is_over(layout.bounds()) {
mouse::Interaction::Idle
} else {
mouse::Interaction::None
}
}
} }
impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::Renderer> impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
@ -948,73 +962,74 @@ impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::
feature = "winit", feature = "winit",
feature = "surface-message" feature = "surface-message"
))] ))]
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) { if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
if let Some((new_root, new_ms)) = new_root { && let Some((new_root, new_ms)) = new_root
use iced_runtime::platform_specific::wayland::popup::{ {
SctkPopupSettings, SctkPositioner, use iced_runtime::platform_specific::wayland::popup::{
SctkPopupSettings, SctkPositioner,
};
let overlay_offset = Point::ORIGIN - viewport.position();
let overlay_cursor = cursor.position().unwrap_or_default() - overlay_offset;
let Some((mut menu, popup_id)) = self.tree.inner.with_data_mut(|state| {
let popup_id = *state
.popup_id
.entry(self.window_id)
.or_insert_with(window::Id::unique);
let active_roots = state
.active_root
.get(self.depth)
.cloned()
.unwrap_or_default();
let root_bounds_list = layout
.children()
.next()
.unwrap()
.children()
.map(|lo| lo.bounds())
.collect();
let mut popup_menu = Menu {
tree: self.tree.clone(),
menu_roots: Cow::Owned(Cow::into_owned(self.menu_roots.clone())),
bounds_expand: self.bounds_expand,
menu_overlays_parent: false,
close_condition: self.close_condition,
item_width: self.item_width,
item_height: self.item_height,
bar_bounds: layout.bounds(),
main_offset: self.main_offset,
cross_offset: self.cross_offset,
root_bounds_list,
path_highlight: self.path_highlight,
style: Cow::Owned(Cow::into_owned(self.style.clone())),
position: Point::new(0., 0.),
is_overlay: false,
window_id: popup_id,
depth: self.depth + 1,
on_surface_action: self.on_surface_action.clone(),
}; };
let overlay_offset = Point::ORIGIN - viewport.position();
let overlay_cursor = cursor.position().unwrap_or_default() - overlay_offset; state.active_root.push(new_root);
let Some((mut menu, popup_id)) = self.tree.inner.with_data_mut(|state| { Some((popup_menu, popup_id))
let popup_id = *state }) else {
.popup_id return;
.entry(self.window_id) };
.or_insert_with(window::Id::unique); // XXX we push a new active root manually instead
let active_roots = state init_root_popup_menu(
.active_root &mut menu,
.get(self.depth) renderer,
.cloned() shell,
.unwrap_or_default(); cursor.position().unwrap_or_default(),
layout.bounds().size(),
let root_bounds_list = layout Vector::new(0., 0.),
.children() layout.bounds(),
.next() self.main_offset as f32,
.unwrap() );
.children() let (anchor_rect, gravity) = self.tree.inner.with_data_mut(|state| {
.map(|lo| lo.bounds())
.collect();
let mut popup_menu = Menu {
tree: self.tree.clone(),
menu_roots: Cow::Owned(Cow::into_owned(self.menu_roots.clone())),
bounds_expand: self.bounds_expand,
menu_overlays_parent: false,
close_condition: self.close_condition,
item_width: self.item_width,
item_height: self.item_height,
bar_bounds: layout.bounds(),
main_offset: self.main_offset,
cross_offset: self.cross_offset,
root_bounds_list,
path_highlight: self.path_highlight,
style: Cow::Owned(Cow::into_owned(self.style.clone())),
position: Point::new(0., 0.),
is_overlay: false,
window_id: popup_id,
depth: self.depth + 1,
on_surface_action: self.on_surface_action.clone(),
};
state.active_root.push(new_root);
Some((popup_menu, popup_id))
}) else {
return;
};
// XXX we push a new active root manually instead
init_root_popup_menu(
&mut menu,
renderer,
shell,
cursor.position().unwrap_or_default(),
layout.bounds().size(),
Vector::new(0., 0.),
layout.bounds(),
self.main_offset as f32,
);
let (anchor_rect, gravity) = self.tree.inner.with_data_mut(|state| {
(state (state
.menu_states .menu_states
.get(self.depth + 1) .get(self.depth + 1)
@ -1043,51 +1058,63 @@ impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::
}) })
}); });
let menu_node = Widget::layout( let menu_node = Widget::layout(
&mut menu, &mut menu,
&mut Tree::empty(), &mut Tree::empty(),
renderer, renderer,
&Limits::NONE.min_width(1.).min_height(1.), &Limits::NONE.min_width(1.).min_height(1.),
); );
let popup_size = menu_node.size(); let popup_size = menu_node.size();
let mut positioner = SctkPositioner { let mut positioner = SctkPositioner {
size: Some(( size: Some((
popup_size.width.ceil() as u32 + 2, popup_size.width.ceil() as u32 + 2,
popup_size.height.ceil() as u32 + 2, popup_size.height.ceil() as u32 + 2,
)), )),
anchor_rect, anchor_rect,
anchor: anchor:
cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::TopRight, cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::TopRight,
gravity, gravity,
reactive: true, reactive: true,
..Default::default() ..Default::default()
}; };
// disable slide_x if it is set in the default // disable slide_x if it is set in the default
positioner.constraint_adjustment &= !(1 << 0); positioner.constraint_adjustment &= !(1 << 0);
let parent = self.window_id; let parent = self.window_id;
shell.publish((self.on_surface_action.as_ref().unwrap())( shell.publish((self.on_surface_action.as_ref().unwrap())(
crate::surface::action::simple_popup( crate::surface::action::simple_popup(
move || SctkPopupSettings { move || SctkPopupSettings {
parent, parent,
id: popup_id, id: popup_id,
positioner: positioner.clone(), positioner: positioner.clone(),
parent_size: None, parent_size: None,
grab: true, grab: true,
close_with_children: false, close_with_children: false,
input_zone: None, input_zone: None,
}, },
Some(move || { Some(move || {
crate::Element::from( crate::Element::from(
crate::widget::container(menu.clone()).center(Length::Fill), crate::widget::container(menu.clone()).center(Length::Fill),
) )
.map(crate::action::app) .map(crate::action::app)
}), }),
), ),
)); ));
}
}
return; fn mouse_interaction(
} &self,
_tree: &Tree,
layout: Layout<'_>,
cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &crate::Renderer,
) -> mouse::Interaction {
if cursor.is_over(layout.bounds()) {
mouse::Interaction::Idle
} else {
mouse::Interaction::None
} }
} }
} }
@ -1331,7 +1358,7 @@ fn process_menu_events<Message: std::clone::Clone>(
shell, shell,
&Rectangle::default(), &Rectangle::default(),
); );
}) });
} }
#[allow(unused_results, clippy::too_many_lines, clippy::too_many_arguments)] #[allow(unused_results, clippy::too_many_lines, clippy::too_many_arguments)]
@ -1343,12 +1370,11 @@ fn process_overlay_events<Message>(
view_cursor: Cursor, view_cursor: Cursor,
overlay_cursor: Point, overlay_cursor: Point,
cross_offset: f32, cross_offset: f32,
_shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
) -> (Option<(usize, MenuState)>, event::Status) ) -> Option<(usize, MenuState)>
where where
Message: std::clone::Clone, Message: std::clone::Clone,
{ {
use event::Status::{Captured, Ignored};
/* /*
if no active root || pressed: if no active root || pressed:
return return
@ -1431,8 +1457,8 @@ where
state.open = false; state.open = false;
} }
} }
shell.capture_event();
return (new_menu_root, Captured); return new_menu_root;
}; };
let last_menu_bounds = &last_menu_state.menu_bounds; let last_menu_bounds = &last_menu_state.menu_bounds;
@ -1446,7 +1472,8 @@ where
{ {
last_menu_state.index = None; last_menu_state.index = None;
return (new_menu_root, Captured); shell.capture_event();
return new_menu_root;
} }
// calc new index // calc new index
@ -1461,7 +1488,7 @@ where
}; };
if state.pressed { if state.pressed {
return (new_menu_root, Ignored); return new_menu_root;
} }
let roots = active_root.iter().skip(1).fold( let roots = active_root.iter().skip(1).fold(
&menu.menu_roots[active_root[0]].children, &menu.menu_roots[active_root[0]].children,
@ -1494,7 +1521,7 @@ where
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) && remove { if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland)) && remove {
if let Some(id) = state.popup_id.remove(&menu.window_id) { if let Some(id) = state.popup_id.remove(&menu.window_id) {
state.active_root.truncate(menu.depth + 1); state.active_root.truncate(menu.depth + 1);
_shell.publish((menu.on_surface_action.as_ref().unwrap())({ shell.publish((menu.on_surface_action.as_ref().unwrap())({
crate::surface::action::destroy_popup(id) crate::surface::action::destroy_popup(id)
})); }));
} }
@ -1555,7 +1582,8 @@ where
state.menu_states.truncate(menu.depth + 1); state.menu_states.truncate(menu.depth + 1);
} }
(new_menu_root, Captured) shell.capture_event();
new_menu_root
}) })
} }

View file

@ -20,7 +20,7 @@ use iced::clipboard::mime::AllowedMimeTypes;
use iced::touch::Finger; use iced::touch::Finger;
use iced::{ use iced::{
Alignment, Background, Color, Event, Length, Padding, Rectangle, Size, Task, Vector, alignment, Alignment, Background, Color, Event, Length, Padding, Rectangle, Size, Task, Vector, alignment,
event, keyboard, mouse, touch, window, keyboard, mouse, touch, window,
}; };
use iced_core::mouse::ScrollDelta; use iced_core::mouse::ScrollDelta;
use iced_core::text::{self, Ellipsize, LineHeight, Renderer as TextRenderer, Shaping, Wrapping}; use iced_core::text::{self, Ellipsize, LineHeight, Renderer as TextRenderer, Shaping, Wrapping};
@ -36,7 +36,6 @@ use std::collections::HashSet;
use std::collections::hash_map::DefaultHasher; use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::mem;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
thread_local! { thread_local! {
@ -609,27 +608,26 @@ where
.text .text
.get(button) .get(button)
.zip(state.paragraphs.entry(button)) .zip(state.paragraphs.entry(button))
&& !text.is_empty()
{ {
if !text.is_empty() { icon_spacing = f32::from(self.button_spacing);
icon_spacing = f32::from(self.button_spacing); let paragraph = entry.or_insert_with(|| {
let paragraph = entry.or_insert_with(|| { crate::Plain::new(Text {
crate::Plain::new(Text { content: text.to_string(), // TODO should we just use String at this point?
content: text.to_string(), // TODO should we just use String at this point? size: iced::Pixels(self.font_size),
size: iced::Pixels(self.font_size), bounds: Size::INFINITE,
bounds: Size::INFINITE, font,
font, align_x: text::Alignment::Left,
align_x: text::Alignment::Left, align_y: alignment::Vertical::Center,
align_y: alignment::Vertical::Center, shaping: Shaping::Advanced,
shaping: Shaping::Advanced, wrapping: Wrapping::default(),
wrapping: Wrapping::default(), ellipsize: Ellipsize::default(),
ellipsize: Ellipsize::default(), line_height: self.line_height,
line_height: self.line_height, })
}) });
});
let size = paragraph.min_bounds(); let size = paragraph.min_bounds();
width += size.width; width += size.width;
}
} }
// Add indent to measurement if found. // Add indent to measurement if found.
@ -895,10 +893,10 @@ where
} }
// Unfocus if another segmented control was focused. // Unfocus if another segmented control was focused.
if let Some(f) = state.focused.as_ref() { if let Some(f) = state.focused.as_ref()
if f.updated_at != LAST_FOCUS_UPDATE.with(|f| f.get()) { && f.updated_at != LAST_FOCUS_UPDATE.with(|f| f.get())
state.unfocus(); {
} state.unfocus();
} }
} }
@ -1162,6 +1160,9 @@ where
None::<fn(_, _) -> Message>, None::<fn(_, _) -> Message>,
on_drop, on_drop,
); );
if matches!(ret, iced::event::Status::Captured) {
shell.capture_event();
}
if let Some(msg) = maybe_msg { if let Some(msg) = maybe_msg {
log::trace!( log::trace!(
target: TAB_REORDER_LOG_TARGET, target: TAB_REORDER_LOG_TARGET,
@ -1200,9 +1201,8 @@ where
} }
Event::Touch(touch::Event::FingerLifted { id, .. }) => { Event::Touch(touch::Event::FingerLifted { id, .. }) => {
state.fingers_pressed.remove(&id); state.fingers_pressed.remove(id);
} }
_ => (), _ => (),
} }
@ -1301,27 +1301,26 @@ where
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
) )
&& !over_close_button && !over_close_button
&& let Some(position) = cursor_position.position()
{ {
if let Some(position) = cursor_position.position() { state.tab_drag_candidate = Some(TabDragCandidate {
state.tab_drag_candidate = Some(TabDragCandidate { entity: key,
entity: key, bounds,
bounds, origin: position,
origin: position, });
}); if let Some(tab_drag) = self.tab_drag.as_ref() {
if let Some(tab_drag) = self.tab_drag.as_ref() { log::trace!(
log::trace!( target: TAB_REORDER_LOG_TARGET,
target: TAB_REORDER_LOG_TARGET, "tab drag candidate entity={:?} origin=({:.2},{:.2}) bounds=({:.2},{:.2},{:.2},{:.2}) threshold={}",
"tab drag candidate entity={:?} origin=({:.2},{:.2}) bounds=({:.2},{:.2},{:.2},{:.2}) threshold={}", key,
key, position.x,
position.x, position.y,
position.y, bounds.x,
bounds.x, bounds.y,
bounds.y, bounds.width,
bounds.width, bounds.height,
bounds.height, tab_drag.threshold
tab_drag.threshold );
);
}
} }
} }
@ -1330,40 +1329,35 @@ where
} }
if let Some(on_activate) = self.on_activate.as_ref() { if let Some(on_activate) = self.on_activate.as_ref() {
if is_pressed(&event) { if is_pressed(event) {
state.pressed_item = Some(Item::Tab(key)); state.pressed_item = Some(Item::Tab(key));
} else if is_lifted(&event) { } else if is_lifted(&event) && self.button_is_pressed(state, key) {
if self.button_is_pressed(state, key) { shell.publish(on_activate(key));
shell.publish(on_activate(key)); state.set_focused();
state.set_focused(); state.focused_item = Item::Tab(key);
state.focused_item = Item::Tab(key); state.pressed_item = None;
state.pressed_item = None; shell.capture_event();
shell.capture_event(); return;
return;
}
} }
} }
// Present a context menu on a right click event. // Present a context menu on a right click event.
if self.context_menu.is_some() { if self.context_menu.is_some()
if let Some(on_context) = self.on_context.as_ref() { && let Some(on_context) = self.on_context.as_ref()
if right_button_released(&event) && (right_button_released(&event)
|| (touch_lifted(&event) && fingers_pressed == 2) || (touch_lifted(&event) && fingers_pressed == 2))
{ {
state.show_context = Some(key); state.show_context = Some(key);
state.context_cursor = state.context_cursor = cursor_position.position().unwrap_or_default();
cursor_position.position().unwrap_or_default();
state.menu_state.inner.with_data_mut(|data| { state.menu_state.inner.with_data_mut(|data| {
data.open = true; data.open = true;
data.view_cursor = cursor_position; data.view_cursor = cursor_position;
}); });
shell.publish(on_context(key)); shell.publish(on_context(key));
shell.capture_event(); shell.capture_event();
return; return;
}
}
} }
if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)) = if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)) =
@ -1385,57 +1379,56 @@ where
} }
} }
if self.scrollable_focus { if self.scrollable_focus
if let Some(on_activate) = self.on_activate.as_ref() { && let Some(on_activate) = self.on_activate.as_ref()
if let Event::Mouse(mouse::Event::WheelScrolled { delta }) = event { && let Event::Mouse(mouse::Event::WheelScrolled { delta }) = event
let current = Instant::now(); {
let current = Instant::now();
// Permit successive scroll wheel events only after a given delay. // Permit successive scroll wheel events only after a given delay.
if state.wheel_timestamp.is_none_or(|previous| { if state.wheel_timestamp.is_none_or(|previous| {
current.duration_since(previous) > Duration::from_millis(250) current.duration_since(previous) > Duration::from_millis(250)
}) { }) {
state.wheel_timestamp = Some(current); state.wheel_timestamp = Some(current);
match delta { match delta {
ScrollDelta::Lines { y, .. } | ScrollDelta::Pixels { y, .. } => { ScrollDelta::Lines { y, .. } | ScrollDelta::Pixels { y, .. } => {
let mut activate_key = None; let mut activate_key = None;
if *y < 0.0 { if *y < 0.0 {
let mut prev_key = Entity::null(); let mut prev_key = Entity::null();
for key in self.model.order.iter().copied() { for key in self.model.order.iter().copied() {
if self.model.is_active(key) && !prev_key.is_null() { if self.model.is_active(key) && !prev_key.is_null() {
activate_key = Some(prev_key); activate_key = Some(prev_key);
} }
if self.model.is_enabled(key) {
prev_key = key;
}
}
} else if *y > 0.0 {
let mut buttons = self.model.order.iter().copied();
while let Some(key) = buttons.next() {
if self.model.is_active(key) {
for key in buttons {
if self.model.is_enabled(key) { if self.model.is_enabled(key) {
prev_key = key; activate_key = Some(key);
}
}
} else if *y > 0.0 {
let mut buttons = self.model.order.iter().copied();
while let Some(key) = buttons.next() {
if self.model.is_active(key) {
for key in buttons {
if self.model.is_enabled(key) {
activate_key = Some(key);
break;
}
}
break; break;
} }
} }
} break;
if let Some(key) = activate_key {
shell.publish(on_activate(key));
state.set_focused();
state.focused_item = Item::Tab(key);
shell.capture_event();
return;
} }
} }
} }
if let Some(key) = activate_key {
shell.publish(on_activate(key));
state.set_focused();
state.focused_item = Item::Tab(key);
shell.capture_event();
return;
}
} }
} }
} }
@ -1460,31 +1453,27 @@ where
if let (Some(tab_drag), Some(candidate)) = if let (Some(tab_drag), Some(candidate)) =
(self.tab_drag.as_ref(), state.tab_drag_candidate) (self.tab_drag.as_ref(), state.tab_drag_candidate)
&& let Event::Mouse(mouse::Event::CursorMoved { .. }) = event
&& let Some(position) = cursor_position.position()
&& position.distance(candidate.origin) >= tab_drag.threshold
&& let Some(candidate) = state.tab_drag_candidate.take()
{ {
if let Event::Mouse(mouse::Event::CursorMoved { .. }) = event { log::trace!(
if let Some(position) = cursor_position.position() { target: TAB_REORDER_LOG_TARGET,
if position.distance(candidate.origin) >= tab_drag.threshold { "tab drag threshold met entity={:?} distance={:.2} threshold={}",
if let Some(candidate) = state.tab_drag_candidate.take() { candidate.entity,
log::trace!( position.distance(candidate.origin),
target: TAB_REORDER_LOG_TARGET, tab_drag.threshold
"tab drag threshold met entity={:?} distance={:.2} threshold={}", );
candidate.entity, if self.start_tab_drag(
position.distance(candidate.origin), state,
tab_drag.threshold candidate.entity,
); candidate.bounds,
if self.start_tab_drag( position,
state, clipboard,
candidate.entity, ) {
candidate.bounds, shell.capture_event();
position, return;
clipboard,
) {
shell.capture_event();
return;
}
}
}
}
} }
} }
@ -1504,55 +1493,53 @@ where
{ {
state.focused_visible = true; state.focused_visible = true;
return if *modifiers == keyboard::Modifiers::SHIFT { return if *modifiers == keyboard::Modifiers::SHIFT {
self.focus_previous(state, shell) self.focus_previous(state, shell);
} else if modifiers.is_empty() { } else if modifiers.is_empty() {
self.focus_next(state, shell) self.focus_next(state, shell);
}; };
} }
if let Some(on_activate) = self.on_activate.as_ref() { if let Some(on_activate) = self.on_activate.as_ref()
if let Event::Keyboard(keyboard::Event::KeyReleased { && let Event::Keyboard(keyboard::Event::KeyReleased {
key: keyboard::Key::Named(keyboard::key::Named::Enter), key: keyboard::Key::Named(keyboard::key::Named::Enter),
.. ..
}) = event }) = event
{ {
match state.focused_item { match state.focused_item {
Item::Tab(entity) => { Item::Tab(entity) => {
shell.publish(on_activate(entity)); shell.publish(on_activate(entity));
}
Item::PrevButton => {
if self.prev_tab_sensitive(state) {
state.buttons_offset -= 1;
// If the change would cause it to be insensitive, focus the first tab.
if !self.prev_tab_sensitive(state) {
if let Some(first) = self.first_tab(state) {
state.focused_item = Item::Tab(first);
}
}
}
}
Item::NextButton => {
if self.next_tab_sensitive(state) {
state.buttons_offset += 1;
// If the change would cause it to be insensitive, focus the last tab.
if !self.next_tab_sensitive(state) {
if let Some(last) = self.last_tab(state) {
state.focused_item = Item::Tab(last);
}
}
}
}
Item::None | Item::Set => (),
} }
shell.capture_event(); Item::PrevButton => {
return; if self.prev_tab_sensitive(state) {
state.buttons_offset -= 1;
// If the change would cause it to be insensitive, focus the first tab.
if !self.prev_tab_sensitive(state)
&& let Some(first) = self.first_tab(state)
{
state.focused_item = Item::Tab(first);
}
}
}
Item::NextButton => {
if self.next_tab_sensitive(state) {
state.buttons_offset += 1;
// If the change would cause it to be insensitive, focus the last tab.
if !self.next_tab_sensitive(state)
&& let Some(last) = self.last_tab(state)
{
state.focused_item = Item::Tab(last);
}
}
}
Item::None | Item::Set => (),
} }
shell.capture_event();
} }
} }
} }
@ -1794,22 +1781,22 @@ where
let original_bounds = bounds; let original_bounds = bounds;
let center_y = bounds.center_y(); let center_y = bounds.center_y();
if show_drop_hint_marker { if show_drop_hint_marker
if matches!( && matches!(
drop_hint_marker, drop_hint_marker,
Some(DropHint { Some(DropHint {
entity, entity,
side: DropSide::Before side: DropSide::Before
}) if entity == key }) if entity == key
) { )
draw_drop_indicator( {
renderer, draw_drop_indicator(
original_bounds, renderer,
DropSide::Before, original_bounds,
Self::VERTICAL, DropSide::Before,
appearance.active.text_color, Self::VERTICAL,
); appearance.active.text_color,
} );
} }
let menu_open = || { let menu_open = || {
@ -1882,41 +1869,41 @@ where
let mut indent_padding = 0.0; let mut indent_padding = 0.0;
// Adjust bounds by indent // Adjust bounds by indent
if let Some(indent) = self.model.indent(key) { if let Some(indent) = self.model.indent(key)
if indent > 0 { && indent > 0
let adjustment = f32::from(indent) * f32::from(self.indent_spacing); {
bounds.x += adjustment; let adjustment = f32::from(indent) * f32::from(self.indent_spacing);
bounds.width -= adjustment; bounds.x += adjustment;
bounds.width -= adjustment;
// Draw indent line // Draw indent line
if let crate::theme::SegmentedButton::FileNav = self.style { if let crate::theme::SegmentedButton::FileNav = self.style
if indent > 1 { && indent > 1
indent_padding = 7.0; {
indent_padding = 7.0;
for level in 1..indent { for level in 1..indent {
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
bounds: Rectangle { bounds: Rectangle {
x: (level as f32) x: (level as f32)
.mul_add(-(self.indent_spacing as f32), bounds.x) .mul_add(-(self.indent_spacing as f32), bounds.x)
+ indent_padding, + indent_padding,
width: 1.0, width: 1.0,
..bounds ..bounds
}, },
border: Border { border: Border {
radius: rad_0.into(), radius: rad_0.into(),
..Default::default() ..Default::default()
}, },
shadow: Shadow::default(), shadow: Shadow::default(),
snap: true, snap: true,
}, },
divider_background, divider_background,
); );
}
indent_padding += 4.0;
}
} }
indent_padding += 4.0;
} }
} }
@ -1990,40 +1977,35 @@ where
bounds.x += offset; bounds.x += offset;
} else { } else {
// Draw the selection indicator if widget is a segmented selection, and the item is selected. // Draw the selection indicator if widget is a segmented selection, and the item is selected.
if key_is_active { if key_is_active && let crate::theme::SegmentedButton::Control = self.style {
if let crate::theme::SegmentedButton::Control = self.style { let mut image_bounds = bounds;
let mut image_bounds = bounds; image_bounds.y = center_y - 8.0;
image_bounds.y = center_y - 8.0;
draw_icon::<Message>( draw_icon::<Message>(
renderer, renderer,
theme, theme,
style, style,
cursor, cursor,
viewport, viewport,
status_appearance.text_color, status_appearance.text_color,
Rectangle { Rectangle {
width: 16.0, width: 16.0,
height: 16.0, height: 16.0,
..image_bounds ..image_bounds
}, },
crate::widget::icon( crate::widget::icon(match crate::widget::common::object_select().data() {
match crate::widget::common::object_select().data() { crate::iced_core::svg::Data::Bytes(bytes) => {
crate::iced_core::svg::Data::Bytes(bytes) => { crate::widget::icon::from_svg_bytes(bytes.as_ref()).symbolic(true)
crate::widget::icon::from_svg_bytes(bytes.as_ref()) }
.symbolic(true) crate::iced_core::svg::Data::Path(path) => {
} crate::widget::icon::from_path(path.clone())
crate::iced_core::svg::Data::Path(path) => { }
crate::widget::icon::from_path(path.clone()) }),
} );
},
),
);
let offset = 16.0 + f32::from(self.button_spacing); let offset = 16.0 + f32::from(self.button_spacing);
bounds.x += offset; bounds.x += offset;
}
} }
} }

View file

@ -248,7 +248,7 @@ where
clipboard, clipboard,
shell, shell,
&layout.bounds(), &layout.bounds(),
) );
} }
fn mouse_interaction( fn mouse_interaction(