fix(segmented_button): hover state handling
when hover state changes, paragraphs also need to be updated. I'll make a not to check this again after the rebase though.
This commit is contained in:
parent
f1c43f79ab
commit
9fcd449611
1 changed files with 106 additions and 76 deletions
|
|
@ -242,6 +242,49 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_entity_paragraph(&mut self, state: &mut LocalState, key: Entity) {
|
||||||
|
if let Some(text) = self.model.text.get(key) {
|
||||||
|
let font = if self.button_is_focused(state, key) {
|
||||||
|
self.font_active
|
||||||
|
} else if state.show_context.is_some() || self.button_is_hovered(state, key) {
|
||||||
|
self.font_hovered
|
||||||
|
} else if self.model.is_active(key) {
|
||||||
|
self.font_active
|
||||||
|
} else {
|
||||||
|
self.font_inactive
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
text.hash(&mut hasher);
|
||||||
|
font.hash(&mut hasher);
|
||||||
|
let text_hash = hasher.finish();
|
||||||
|
|
||||||
|
if let Some(prev_hash) = state.text_hashes.insert(key, text_hash) {
|
||||||
|
if prev_hash == text_hash {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = Text {
|
||||||
|
content: text.as_ref(),
|
||||||
|
size: iced::Pixels(self.font_size),
|
||||||
|
bounds: Size::INFINITY,
|
||||||
|
font,
|
||||||
|
horizontal_alignment: alignment::Horizontal::Left,
|
||||||
|
vertical_alignment: alignment::Vertical::Center,
|
||||||
|
shaping: Shaping::Advanced,
|
||||||
|
wrapping: Wrapping::None,
|
||||||
|
line_height: self.line_height,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(paragraph) = state.paragraphs.get_mut(key) {
|
||||||
|
paragraph.update(text);
|
||||||
|
} else {
|
||||||
|
state.paragraphs.insert(key, crate::Plain::new(text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn context_menu(mut self, context_menu: Option<Vec<menu::Tree<Message>>>) -> Self
|
pub fn context_menu(mut self, context_menu: Option<Vec<menu::Tree<Message>>>) -> Self
|
||||||
where
|
where
|
||||||
Message: Clone + 'static,
|
Message: Clone + 'static,
|
||||||
|
|
@ -761,6 +804,14 @@ where
|
||||||
SelectionMode: Default,
|
SelectionMode: Default,
|
||||||
Message: 'static + Clone,
|
Message: 'static + Clone,
|
||||||
{
|
{
|
||||||
|
fn id(&self) -> Option<widget::Id> {
|
||||||
|
Some(self.id.0.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_id(&mut self, id: widget::Id) {
|
||||||
|
self.id = Id(id);
|
||||||
|
}
|
||||||
|
|
||||||
fn children(&self) -> Vec<Tree> {
|
fn children(&self) -> Vec<Tree> {
|
||||||
let mut children = Vec::new();
|
let mut children = Vec::new();
|
||||||
|
|
||||||
|
|
@ -812,46 +863,7 @@ where
|
||||||
let state = tree.state.downcast_mut::<LocalState>();
|
let state = tree.state.downcast_mut::<LocalState>();
|
||||||
|
|
||||||
for key in self.model.order.iter().copied() {
|
for key in self.model.order.iter().copied() {
|
||||||
if let Some(text) = self.model.text.get(key) {
|
self.update_entity_paragraph(state, key);
|
||||||
let font = if self.button_is_focused(state, key) {
|
|
||||||
self.font_active
|
|
||||||
} else if state.show_context.is_some() || self.button_is_hovered(state, key) {
|
|
||||||
self.font_hovered
|
|
||||||
} else if self.model.is_active(key) {
|
|
||||||
self.font_active
|
|
||||||
} else {
|
|
||||||
self.font_inactive
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut hasher = DefaultHasher::new();
|
|
||||||
text.hash(&mut hasher);
|
|
||||||
font.hash(&mut hasher);
|
|
||||||
let text_hash = hasher.finish();
|
|
||||||
|
|
||||||
if let Some(prev_hash) = state.text_hashes.insert(key, text_hash) {
|
|
||||||
if prev_hash == text_hash {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let text = Text {
|
|
||||||
content: text.as_ref(),
|
|
||||||
size: iced::Pixels(self.font_size),
|
|
||||||
bounds: Size::INFINITY,
|
|
||||||
font,
|
|
||||||
horizontal_alignment: alignment::Horizontal::Left,
|
|
||||||
vertical_alignment: alignment::Vertical::Center,
|
|
||||||
shaping: Shaping::Advanced,
|
|
||||||
wrapping: Wrapping::None,
|
|
||||||
line_height: self.line_height,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(paragraph) = state.paragraphs.get_mut(key) {
|
|
||||||
paragraph.update(text);
|
|
||||||
} else {
|
|
||||||
state.paragraphs.insert(key, crate::Plain::new(text));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Diff the context menu
|
// Diff the context menu
|
||||||
|
|
@ -899,9 +911,8 @@ where
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &iced::Rectangle,
|
_viewport: &iced::Rectangle,
|
||||||
) -> event::Status {
|
) -> event::Status {
|
||||||
let bounds = layout.bounds();
|
let my_bounds = layout.bounds();
|
||||||
let state = tree.state.downcast_mut::<LocalState>();
|
let state = tree.state.downcast_mut::<LocalState>();
|
||||||
state.hovered = Item::None;
|
|
||||||
|
|
||||||
let my_id = self.get_drag_id();
|
let my_id = self.get_drag_id();
|
||||||
|
|
||||||
|
|
@ -938,7 +949,7 @@ where
|
||||||
},
|
},
|
||||||
) if Some(my_id) == *id => {
|
) if Some(my_id) == *id => {
|
||||||
let entity = self
|
let entity = self
|
||||||
.variant_bounds(state, bounds)
|
.variant_bounds(state, my_bounds)
|
||||||
.filter_map(|item| match item {
|
.filter_map(|item| match item {
|
||||||
ItemBounds::Button(entity, bounds) => Some((entity, bounds)),
|
ItemBounds::Button(entity, bounds) => Some((entity, bounds)),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|
@ -947,7 +958,7 @@ where
|
||||||
.map(|(key, _)| key);
|
.map(|(key, _)| key);
|
||||||
state.drop_hint = self.drop_hint_for_position(
|
state.drop_hint = self.drop_hint_for_position(
|
||||||
state,
|
state,
|
||||||
bounds,
|
my_bounds,
|
||||||
Point::new(*x as f32, *y as f32),
|
Point::new(*x as f32, *y as f32),
|
||||||
);
|
);
|
||||||
self.emit_drop_hint(shell, state.drop_hint);
|
self.emit_drop_hint(shell, state.drop_hint);
|
||||||
|
|
@ -979,9 +990,6 @@ where
|
||||||
if matches!(leave, OfferEvent::Leave | OfferEvent::LeaveDestination)
|
if matches!(leave, OfferEvent::Leave | OfferEvent::LeaveDestination)
|
||||||
&& Some(my_id) == *id =>
|
&& Some(my_id) == *id =>
|
||||||
{
|
{
|
||||||
if matches!(leave, OfferEvent::Leave) {
|
|
||||||
state.dragging_tab = None;
|
|
||||||
}
|
|
||||||
state.drop_hint = None;
|
state.drop_hint = None;
|
||||||
self.emit_drop_hint(shell, state.drop_hint);
|
self.emit_drop_hint(shell, state.drop_hint);
|
||||||
if let Some(Some(entity)) = entity {
|
if let Some(Some(entity)) = entity {
|
||||||
|
|
@ -1001,7 +1009,7 @@ where
|
||||||
"offer motion id={my_id:?} cursor=({x},{y}) current_entity={entity:?}"
|
"offer motion id={my_id:?} cursor=({x},{y}) current_entity={entity:?}"
|
||||||
);
|
);
|
||||||
let new = self
|
let new = self
|
||||||
.variant_bounds(state, bounds)
|
.variant_bounds(state, my_bounds)
|
||||||
.filter_map(|item| match item {
|
.filter_map(|item| match item {
|
||||||
ItemBounds::Button(entity, bounds) => Some((entity, bounds)),
|
ItemBounds::Button(entity, bounds) => Some((entity, bounds)),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|
@ -1018,11 +1026,15 @@ where
|
||||||
);
|
);
|
||||||
state.drop_hint = self.drop_hint_for_position(
|
state.drop_hint = self.drop_hint_for_position(
|
||||||
state,
|
state,
|
||||||
bounds,
|
my_bounds,
|
||||||
Point::new(*x as f32, *y as f32),
|
Point::new(*x as f32, *y as f32),
|
||||||
);
|
);
|
||||||
self.emit_drop_hint(shell, state.drop_hint);
|
self.emit_drop_hint(shell, state.drop_hint);
|
||||||
if Some(Some(new_entity)) != entity {
|
if Some(Some(new_entity)) != entity {
|
||||||
|
state.hovered = Item::Tab(new_entity);
|
||||||
|
for key in self.model.order.iter().copied() {
|
||||||
|
self.update_entity_paragraph(state, key);
|
||||||
|
}
|
||||||
let prev_action = state
|
let prev_action = state
|
||||||
.dnd_state
|
.dnd_state
|
||||||
.drag_offer
|
.drag_offer
|
||||||
|
|
@ -1039,6 +1051,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if entity.is_some() {
|
} else if entity.is_some() {
|
||||||
|
state.hovered = Item::None;
|
||||||
|
for key in self.model.order.iter().copied() {
|
||||||
|
self.update_entity_paragraph(state, key);
|
||||||
|
}
|
||||||
log::trace!(
|
log::trace!(
|
||||||
target: TAB_REORDER_LOG_TARGET,
|
target: TAB_REORDER_LOG_TARGET,
|
||||||
"offer motion leaving id={my_id:?}"
|
"offer motion leaving id={my_id:?}"
|
||||||
|
|
@ -1124,31 +1140,24 @@ where
|
||||||
|
|
||||||
self.emit_drop_hint(shell, state.drop_hint);
|
self.emit_drop_hint(shell, state.drop_hint);
|
||||||
if let Some(event) = pending_reorder {
|
if let Some(event) = pending_reorder {
|
||||||
|
state.focused_item = Item::Tab(event.dragged);
|
||||||
|
state.hovered = Item::None;
|
||||||
|
for key in self.model.order.iter().copied() {
|
||||||
|
self.update_entity_paragraph(state, key);
|
||||||
|
}
|
||||||
if let Some(on_reorder) = self.on_reorder.as_ref() {
|
if let Some(on_reorder) = self.on_reorder.as_ref() {
|
||||||
shell.publish(on_reorder(event));
|
shell.publish(on_reorder(event));
|
||||||
|
return event::Status::Captured;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
} else {
|
|
||||||
log::trace!(
|
|
||||||
target: TAB_REORDER_LOG_TARGET,
|
|
||||||
"data received without entity id={my_id:?}"
|
|
||||||
);
|
|
||||||
state.drop_hint = None;
|
|
||||||
|
|
||||||
self.emit_drop_hint(shell, state.drop_hint);
|
|
||||||
if let Some(event) = pending_reorder {
|
|
||||||
if let Some(on_reorder) = self.on_reorder.as_ref() {
|
|
||||||
shell.publish(on_reorder(event));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cursor_position.is_over(bounds) {
|
if cursor_position.is_over(my_bounds) {
|
||||||
let fingers_pressed = state.fingers_pressed.len();
|
let fingers_pressed = state.fingers_pressed.len();
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
|
|
@ -1166,10 +1175,14 @@ where
|
||||||
// Check for clicks on the previous and next tab buttons, when tabs are collapsed.
|
// Check for clicks on the previous and next tab buttons, when tabs are collapsed.
|
||||||
if state.collapsed {
|
if state.collapsed {
|
||||||
// Check if the prev tab button was clicked.
|
// Check if the prev tab button was clicked.
|
||||||
if cursor_position.is_over(prev_tab_bounds(&bounds, f32::from(self.button_height)))
|
if cursor_position
|
||||||
|
.is_over(prev_tab_bounds(&my_bounds, f32::from(self.button_height)))
|
||||||
&& self.prev_tab_sensitive(state)
|
&& self.prev_tab_sensitive(state)
|
||||||
{
|
{
|
||||||
state.hovered = Item::PrevButton;
|
state.hovered = Item::PrevButton;
|
||||||
|
for key in self.model.order.iter().copied() {
|
||||||
|
self.update_entity_paragraph(state, key);
|
||||||
|
}
|
||||||
if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||||
| Event::Touch(touch::Event::FingerLifted { .. }) = event
|
| Event::Touch(touch::Event::FingerLifted { .. }) = event
|
||||||
{
|
{
|
||||||
|
|
@ -1178,11 +1191,13 @@ where
|
||||||
} else {
|
} else {
|
||||||
// Check if the next tab button was clicked.
|
// Check if the next tab button was clicked.
|
||||||
if cursor_position
|
if cursor_position
|
||||||
.is_over(next_tab_bounds(&bounds, f32::from(self.button_height)))
|
.is_over(next_tab_bounds(&my_bounds, f32::from(self.button_height)))
|
||||||
&& self.next_tab_sensitive(state)
|
&& self.next_tab_sensitive(state)
|
||||||
{
|
{
|
||||||
state.hovered = Item::NextButton;
|
state.hovered = Item::NextButton;
|
||||||
|
for key in self.model.order.iter().copied() {
|
||||||
|
self.update_entity_paragraph(state, key);
|
||||||
|
}
|
||||||
if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||||
| Event::Touch(touch::Event::FingerLifted { .. }) = event
|
| Event::Touch(touch::Event::FingerLifted { .. }) = event
|
||||||
{
|
{
|
||||||
|
|
@ -1193,7 +1208,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
for (key, bounds) in self
|
for (key, bounds) in self
|
||||||
.variant_bounds(state, bounds)
|
.variant_bounds(state, my_bounds)
|
||||||
.filter_map(|item| match item {
|
.filter_map(|item| match item {
|
||||||
ItemBounds::Button(entity, bounds) => Some((entity, bounds)),
|
ItemBounds::Button(entity, bounds) => Some((entity, bounds)),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|
@ -1203,7 +1218,12 @@ where
|
||||||
if cursor_position.is_over(bounds) {
|
if cursor_position.is_over(bounds) {
|
||||||
if self.model.items[key].enabled {
|
if self.model.items[key].enabled {
|
||||||
// Record that the mouse is hovering over this button.
|
// Record that the mouse is hovering over this button.
|
||||||
state.hovered = Item::Tab(key);
|
if state.hovered != Item::Tab(key) {
|
||||||
|
state.hovered = Item::Tab(key);
|
||||||
|
for key in self.model.order.iter().copied() {
|
||||||
|
self.update_entity_paragraph(state, key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let close_button_bounds =
|
let close_button_bounds =
|
||||||
close_bounds(bounds, f32::from(self.close_icon.size));
|
close_bounds(bounds, f32::from(self.close_icon.size));
|
||||||
|
|
@ -1320,6 +1340,9 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
} else if state.hovered == Item::Tab(key) {
|
||||||
|
state.hovered = Item::None;
|
||||||
|
self.update_entity_paragraph(state, key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1377,15 +1400,22 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if state.is_focused() {
|
} else {
|
||||||
// Unfocus on clicks outside of the boundaries of the segmented button.
|
if let Item::Tab(key) = std::mem::replace(&mut state.hovered, Item::None) {
|
||||||
if is_pressed(&event) {
|
for key in self.model.order.iter().copied() {
|
||||||
state.unfocus();
|
self.update_entity_paragraph(state, key);
|
||||||
state.pressed_item = None;
|
}
|
||||||
return event::Status::Ignored;
|
}
|
||||||
|
if state.is_focused() {
|
||||||
|
// Unfocus on clicks outside of the boundaries of the segmented button.
|
||||||
|
if is_pressed(&event) {
|
||||||
|
state.unfocus();
|
||||||
|
state.pressed_item = None;
|
||||||
|
return event::Status::Ignored;
|
||||||
|
}
|
||||||
|
} else if is_lifted(&event) {
|
||||||
|
state.pressed_item = None;
|
||||||
}
|
}
|
||||||
} else if is_lifted(&event) {
|
|
||||||
state.pressed_item = None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (Some(tab_drag), Some(candidate)) =
|
if let (Some(tab_drag), Some(candidate)) =
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue