Skip to content

Commit 2051f49

Browse files
committed
feat(stack): track and draw Tab hover state
1 parent 59d652e commit 2051f49

File tree

1 file changed

+65
-41
lines changed

1 file changed

+65
-41
lines changed

src/shell/element/stack/tab.rs

Lines changed: 65 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ use cosmic::{
88
alignment, event,
99
layout::{Layout, Limits, Node},
1010
mouse, overlay, renderer,
11-
widget::{operation::Operation, tree::Tree, Id, Widget},
11+
widget::{
12+
operation::Operation,
13+
tree::{self, Tree},
14+
Id, Widget,
15+
},
1216
Border, Clipboard, Color, Length, Rectangle, Shell, Size,
1317
},
1418
iced_widget::scrollable::AbsoluteOffset,
@@ -53,46 +57,52 @@ impl From<TabRuleTheme> for theme::Rule {
5357

5458
#[derive(Clone, Copy)]
5559
pub(super) enum TabBackgroundTheme {
60+
/// Selected active stack
5661
ActiveActivated,
62+
/// Selected inactive stack
5763
ActiveDeactivated,
64+
/// Not selected
5865
Default,
5966
}
6067

61-
impl From<TabBackgroundTheme> for theme::Container<'_> {
62-
fn from(background_theme: TabBackgroundTheme) -> Self {
63-
match background_theme {
64-
TabBackgroundTheme::ActiveActivated => {
65-
Self::custom(move |theme| widget::container::Style {
66-
icon_color: Some(Color::from(theme.cosmic().accent_text_color())),
67-
text_color: Some(Color::from(theme.cosmic().accent_text_color())),
68-
background: Some(Background::Color(
69-
theme.cosmic().primary.component.selected.into(),
70-
)),
71-
border: Border {
72-
radius: 0.0.into(),
73-
width: 0.0,
74-
color: Color::TRANSPARENT,
75-
},
76-
shadow: Default::default(),
77-
})
78-
}
79-
TabBackgroundTheme::ActiveDeactivated => {
80-
Self::custom(move |theme| widget::container::Style {
81-
icon_color: None,
82-
text_color: None,
83-
background: Some(Background::Color(
84-
theme.cosmic().primary.component.base.into(),
85-
)),
86-
border: Border {
87-
radius: 0.0.into(),
88-
width: 0.0,
89-
color: Color::TRANSPARENT,
90-
},
91-
shadow: Default::default(),
92-
})
68+
impl TabBackgroundTheme {
69+
pub fn into_container_theme(self, hovered: bool) -> theme::Container<'static> {
70+
let (selected, active_stack) = match self {
71+
Self::ActiveActivated => (true, true),
72+
Self::ActiveDeactivated => (true, false),
73+
Self::Default => (false, false),
74+
};
75+
76+
theme::Container::custom(move |theme| {
77+
let cosmic_theme = theme.cosmic();
78+
79+
let text_color = if selected && active_stack {
80+
Some(Color::from(cosmic_theme.accent_text_color()))
81+
} else {
82+
None
83+
};
84+
85+
widget::container::Style {
86+
icon_color: text_color,
87+
text_color,
88+
background: Some(Background::Color(
89+
if hovered {
90+
cosmic_theme.primary.component.hover_state_color()
91+
} else if selected {
92+
cosmic_theme.primary.component.selected_state_color()
93+
} else {
94+
cosmic_theme.primary.component.base
95+
}
96+
.into(),
97+
)),
98+
border: Border {
99+
radius: 0.0.into(),
100+
width: 0.0,
101+
color: Color::TRANSPARENT,
102+
},
103+
shadow: Default::default(),
93104
}
94-
TabBackgroundTheme::Default => Self::Transparent,
95-
}
105+
})
96106
}
97107
}
98108

@@ -105,6 +115,10 @@ pub trait TabMessage: Clone {
105115
fn scrolled() -> Self;
106116
}
107117

118+
struct LocalState {
119+
hovered: bool,
120+
}
121+
108122
pub struct Tab<Message: TabMessage> {
109123
id: Id,
110124
app_icon: Icon,
@@ -209,7 +223,7 @@ impl<Message: TabMessage + 'static> Tab<Message> {
209223
id: self.id,
210224
idx,
211225
active: self.active,
212-
background: self.background_theme.into(),
226+
background: self.background_theme,
213227
elements: items,
214228
press_message: self.press_message,
215229
right_click_message: self.right_click_message,
@@ -228,7 +242,7 @@ pub(super) struct TabInternal<'a, Message: TabMessage> {
228242
id: Id,
229243
idx: usize,
230244
active: bool,
231-
background: theme::Container<'a>,
245+
background: TabBackgroundTheme,
232246
elements: Vec<cosmic::Element<'a, Message>>,
233247
press_message: Option<Message>,
234248
right_click_message: Option<Message>,
@@ -258,6 +272,10 @@ where
258272
Size::new(Length::Fill, Length::Fill)
259273
}
260274

275+
fn state(&self) -> tree::State {
276+
tree::State::new(LocalState { hovered: false })
277+
}
278+
261279
fn layout(&self, tree: &mut Tree, renderer: &cosmic::Renderer, limits: &Limits) -> Node {
262280
let min_size = Size {
263281
height: TAB_HEIGHT as f32,
@@ -332,6 +350,9 @@ where
332350
shell: &mut Shell<'_, Message>,
333351
viewport: &Rectangle,
334352
) -> event::Status {
353+
let state = tree.state.downcast_mut::<LocalState>();
354+
state.hovered = cursor.is_over(layout.bounds());
355+
335356
let status = self
336357
.elements
337358
.iter_mut()
@@ -351,7 +372,7 @@ where
351372
})
352373
.fold(event::Status::Ignored, event::Status::merge);
353374

354-
if status == event::Status::Ignored && cursor.is_over(layout.bounds()) {
375+
if status == event::Status::Ignored && state.hovered {
355376
if matches!(
356377
event,
357378
event::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
@@ -414,7 +435,10 @@ where
414435
viewport: &Rectangle,
415436
) {
416437
use cosmic::widget::container::Catalog;
417-
let style = theme.style(&self.background);
438+
let state = tree.state.downcast_ref::<LocalState>();
439+
440+
let style = theme.style(&self.background.into_container_theme(state.hovered));
441+
let text_color = style.text_color.unwrap_or(renderer_style.text_color);
418442

419443
draw_background(renderer, &style, layout.bounds());
420444

@@ -429,8 +453,8 @@ where
429453
renderer,
430454
theme,
431455
&renderer::Style {
432-
icon_color: style.text_color.unwrap_or(renderer_style.text_color),
433-
text_color: style.text_color.unwrap_or(renderer_style.text_color),
456+
icon_color: text_color,
457+
text_color,
434458
scale_factor: renderer_style.scale_factor,
435459
},
436460
layout,

0 commit comments

Comments
 (0)