Refine notification center and configure native notifications
Keep notification actions and dismiss controls visible, compact and animate history, and add themed icon controls and grouping. Include notification settings, session integration and documentation, on-demand workspaces, and performance baseline notes.
This commit is contained in:
1 parent
a98d7a75c3
commit
ab6bf8b961
16 files changed
+921
-123
No files matched your search
@@ -1,23 +1,61 @@
|
||||
use super::*;
|
||||
use cosmic::iced::widget::scrollable::{Direction, Scrollbar};
|
||||
use cosmic::widget::{column, scrollable};
|
||||
|
||||
const POPUP_WIDTH: u32 = 368;
|
||||
|
||||
fn popup_height(notice: ¬ifications::Notice, hovered: bool, count: usize) -> u32 {
|
||||
fn popup_height(notice: ¬ifications::Notice, count: usize) -> u32 {
|
||||
let body = if notice.body.is_empty() && count == 1 {
|
||||
0
|
||||
} else {
|
||||
22
|
||||
};
|
||||
let actions =
|
||||
if hovered && notice.live && notice.actions.iter().any(|(key, _)| key != "default") {
|
||||
39
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let actions = if notice.live && notice.actions.iter().any(|(key, _)| key != "default") {
|
||||
39
|
||||
} else {
|
||||
0
|
||||
};
|
||||
76 + body + actions + if count > 1 { 4 } else { 0 }
|
||||
}
|
||||
|
||||
fn history_height(center: ¬ifications::Center) -> u32 {
|
||||
if center.entries.is_empty() {
|
||||
return 280;
|
||||
}
|
||||
let groups = center.history_groups();
|
||||
let card_height = |notice: ¬ifications::Notice, count| {
|
||||
let wrapped_lines = if count == 1 {
|
||||
notice
|
||||
.body
|
||||
.lines()
|
||||
.map(|line| line.chars().count().div_ceil(40).max(1))
|
||||
.sum::<usize>()
|
||||
} else {
|
||||
1
|
||||
};
|
||||
popup_height(notice, count)
|
||||
+ wrapped_lines.saturating_sub(1) as u32 * 17
|
||||
+ if count > 1 { 12 } else { 0 }
|
||||
};
|
||||
let cards: u32 = groups
|
||||
.iter()
|
||||
.map(|group| {
|
||||
if group.len() > 1 && center.expanded_apps.contains(&group[0].app) {
|
||||
32 + group
|
||||
.iter()
|
||||
.map(|notice| card_height(notice, 1))
|
||||
.sum::<u32>()
|
||||
+ group.len().saturating_sub(1) as u32 * 8
|
||||
} else {
|
||||
card_height(group[0], group.len())
|
||||
}
|
||||
})
|
||||
.sum();
|
||||
// Header, DND row and section tools share the same compact rhythm as the
|
||||
// cards. Long histories scroll; short histories do not reserve blank space.
|
||||
(170 + cards + groups.len().saturating_sub(1) as u32 * 8).min(620)
|
||||
}
|
||||
|
||||
fn blend(base: Color, foreground: Color, amount: f32) -> Color {
|
||||
Color {
|
||||
r: base.r + (foreground.r - base.r) * amount,
|
||||
@@ -54,6 +92,7 @@ fn card_button<'a>(
|
||||
foreground: Color,
|
||||
hover: Color,
|
||||
round: bool,
|
||||
accessible_name: String,
|
||||
) -> Element<'a, cosmic::Action<Message>> {
|
||||
let style = move |active: bool| button::Style {
|
||||
text_color: Some(foreground),
|
||||
@@ -63,6 +102,7 @@ fn card_button<'a>(
|
||||
..Default::default()
|
||||
};
|
||||
button::custom(content)
|
||||
.name(accessible_name)
|
||||
.width(if round { Length::Shrink } else { Length::Fill })
|
||||
.padding(0)
|
||||
.on_press(cosmic::Action::App(message))
|
||||
@@ -75,13 +115,117 @@ fn card_button<'a>(
|
||||
.into()
|
||||
}
|
||||
|
||||
fn icon_control<'a>(
|
||||
source: &'static [u8],
|
||||
label: &'static str,
|
||||
message: Message,
|
||||
foreground: Color,
|
||||
background: Color,
|
||||
extent: u16,
|
||||
) -> Element<'a, cosmic::Action<Message>> {
|
||||
let control = card_button(
|
||||
container(bar_icon(source, 14, foreground))
|
||||
.center_x(extent)
|
||||
.center_y(extent),
|
||||
message,
|
||||
foreground,
|
||||
background,
|
||||
true,
|
||||
label.into(),
|
||||
);
|
||||
cosmic::widget::tooltip(
|
||||
control,
|
||||
text(label).size(11).class(theme::Text::Color(foreground)),
|
||||
cosmic::widget::tooltip::Position::Left,
|
||||
)
|
||||
.class(theme::Container::custom(move |_| container::Style {
|
||||
background: Some(Background::Color(background)),
|
||||
border: Border {
|
||||
radius: 6.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
text_color: Some(foreground),
|
||||
..Default::default()
|
||||
}))
|
||||
.into()
|
||||
}
|
||||
|
||||
pub(super) struct NotificationSurface {
|
||||
pub id: window::Id,
|
||||
output: wl_output::WlOutput,
|
||||
height: u32,
|
||||
pub motion: Option<motion::PopupMotion>,
|
||||
pub effects: Option<EffectsBinding>,
|
||||
pub ready: bool,
|
||||
regions: motion::Regions,
|
||||
}
|
||||
|
||||
impl NotificationSurface {
|
||||
pub fn animating(&self) -> bool {
|
||||
self.motion
|
||||
.as_ref()
|
||||
.is_some_and(|motion| motion.animating() || motion.closing())
|
||||
}
|
||||
|
||||
pub fn progress(&self) -> f32 {
|
||||
self.motion
|
||||
.as_ref()
|
||||
.map_or(1.0, motion::PopupMotion::progress)
|
||||
}
|
||||
}
|
||||
|
||||
impl FereseShell {
|
||||
pub(super) fn toggle_notification_history(&mut self) -> Task<Message> {
|
||||
if self.notifications.history_open {
|
||||
if let Some(motion) = self
|
||||
.notification_surface
|
||||
.as_mut()
|
||||
.and_then(|surface| surface.motion.as_mut())
|
||||
{
|
||||
if motion.closing() {
|
||||
motion.retarget(1.0, Instant::now());
|
||||
return self.sync_notification_surface();
|
||||
}
|
||||
}
|
||||
return self.close_notification_history();
|
||||
}
|
||||
self.notifications.toggle_history();
|
||||
self.sync_notification_surface()
|
||||
}
|
||||
|
||||
pub(super) fn close_notification_history(&mut self) -> Task<Message> {
|
||||
if let Some(motion) = self
|
||||
.notification_surface
|
||||
.as_mut()
|
||||
.and_then(|surface| surface.motion.as_mut())
|
||||
{
|
||||
if !motion.closing() {
|
||||
motion.retarget(0.0, Instant::now());
|
||||
}
|
||||
return self.animate_notification_history();
|
||||
}
|
||||
self.notifications.history_open = false;
|
||||
self.sync_notification_surface()
|
||||
}
|
||||
|
||||
pub(super) fn animate_notification_history(&mut self) -> Task<Message> {
|
||||
if let Some(surface) = &mut self.notification_surface {
|
||||
if surface
|
||||
.motion
|
||||
.as_ref()
|
||||
.is_some_and(|motion| motion.closing() && !motion.animating())
|
||||
{
|
||||
self.notifications.history_open = false;
|
||||
self.notifications.hovered = None;
|
||||
surface.motion = None;
|
||||
}
|
||||
if let Some(effects) = &surface.effects {
|
||||
let _ = effects.set_opacity(surface.progress());
|
||||
}
|
||||
}
|
||||
self.sync_notification_surface()
|
||||
}
|
||||
|
||||
pub(super) fn sync_notification_surface(&mut self) -> Task<Message> {
|
||||
if self.notifications.ready {
|
||||
self.status.notifications = Some(status::Notifications {
|
||||
@@ -117,17 +261,13 @@ impl FereseShell {
|
||||
return Task::batch(tasks);
|
||||
};
|
||||
let desired_height = if self.notifications.history_open {
|
||||
520
|
||||
history_height(&self.notifications)
|
||||
} else {
|
||||
self.notifications
|
||||
.popup_groups()
|
||||
.iter()
|
||||
.map(|(notice, _, count)| {
|
||||
popup_height(
|
||||
notice,
|
||||
self.notifications.hovered == Some(notice.id),
|
||||
*count,
|
||||
) + if *count > 1 { 12 } else { 0 }
|
||||
popup_height(notice, *count) + if *count > 1 { 12 } else { 0 }
|
||||
})
|
||||
.sum::<u32>()
|
||||
+ count.saturating_sub(1) as u32 * 8
|
||||
@@ -152,6 +292,10 @@ impl FereseShell {
|
||||
id,
|
||||
output: output.clone(),
|
||||
height,
|
||||
motion: None,
|
||||
effects: None,
|
||||
ready: false,
|
||||
regions: Default::default(),
|
||||
});
|
||||
let top = self.config.theme.bar_height + self.config.theme.bar_margin_top as f32 + 12.0;
|
||||
let action = cosmic::surface::action::app_layer_shell::<Self>(
|
||||
@@ -177,6 +321,19 @@ impl FereseShell {
|
||||
);
|
||||
tasks.push(cosmic::task::message(cosmic::Action::Surface(action)));
|
||||
}
|
||||
if self.notifications.history_open {
|
||||
let surface = self.notification_surface.as_mut().unwrap();
|
||||
if surface.motion.is_none() {
|
||||
let mut motion = motion::PopupMotion::new(self.config.animations);
|
||||
if surface.ready {
|
||||
motion.begin(Instant::now());
|
||||
}
|
||||
surface.motion = Some(motion);
|
||||
}
|
||||
if let Some(effects) = &surface.effects {
|
||||
let _ = effects.set_opacity(surface.progress());
|
||||
}
|
||||
}
|
||||
Task::batch(tasks)
|
||||
}
|
||||
|
||||
@@ -200,7 +357,6 @@ impl FereseShell {
|
||||
let divider = tint(palette.border);
|
||||
let well = blend(base, foreground, 0.07);
|
||||
let id = notice.id;
|
||||
let hovered = self.notifications.hovered == Some(id);
|
||||
let mut bold = *SHELL_FONT.get().unwrap().read().unwrap();
|
||||
bold.weight = cosmic::iced::font::Weight::Bold;
|
||||
let app_icon = if notice.app.to_lowercase().starts_with("ferese") || notice.icon.is_empty()
|
||||
@@ -288,7 +444,21 @@ impl FereseShell {
|
||||
text(age_label(notice.received_at.elapsed()))
|
||||
.size(11)
|
||||
.class(theme::Text::Color(muted)),
|
||||
);
|
||||
)
|
||||
.push(icon_control(
|
||||
include_bytes!("../assets/icons/status/close.svg"),
|
||||
"Dismiss notification",
|
||||
if history && count > 1 {
|
||||
Message::RemoveNotificationGroup(notice.app.clone())
|
||||
} else if history {
|
||||
Message::RemoveNotification(id)
|
||||
} else {
|
||||
Message::DismissNotification(id)
|
||||
},
|
||||
muted,
|
||||
well,
|
||||
22,
|
||||
));
|
||||
let mut content = column([]).spacing(4).push(header).push(
|
||||
container(
|
||||
text(notice.title.clone())
|
||||
@@ -311,7 +481,7 @@ impl FereseShell {
|
||||
if !body.is_empty() {
|
||||
content = content.push(
|
||||
container(text(body).size(13).class(theme::Text::Color(muted)))
|
||||
.height(if history {
|
||||
.height(if history && count == 1 {
|
||||
Length::Shrink
|
||||
} else {
|
||||
Length::Fixed(18.0)
|
||||
@@ -320,7 +490,7 @@ impl FereseShell {
|
||||
);
|
||||
}
|
||||
let mut face = column([]).push(container(content).padding([12, 14]).width(Length::Fill));
|
||||
if hovered && notice.live {
|
||||
if notice.live {
|
||||
let available: Vec<_> = notice
|
||||
.actions
|
||||
.iter()
|
||||
@@ -340,13 +510,19 @@ impl FereseShell {
|
||||
);
|
||||
}
|
||||
actions = actions.push(card_button(
|
||||
container(text(label.clone()).font(bold).size(13))
|
||||
.center_x(Length::Fill)
|
||||
.center_y(38),
|
||||
container(
|
||||
text(label.clone())
|
||||
.font(bold)
|
||||
.size(13)
|
||||
.class(theme::Text::Color(accent)),
|
||||
)
|
||||
.center_x(Length::Fill)
|
||||
.center_y(38),
|
||||
Message::InvokeNotification(id, key.clone()),
|
||||
accent,
|
||||
well,
|
||||
false,
|
||||
label.clone(),
|
||||
));
|
||||
}
|
||||
face = face
|
||||
@@ -377,31 +553,9 @@ impl FereseShell {
|
||||
},
|
||||
..Default::default()
|
||||
}));
|
||||
let card: Element<'_, cosmic::Action<Message>> = if hovered {
|
||||
let close = card_button(
|
||||
container(text("×").size(14)).center_x(18).center_y(18),
|
||||
if history {
|
||||
Message::RemoveNotification(id)
|
||||
} else {
|
||||
Message::DismissNotification(id)
|
||||
},
|
||||
muted,
|
||||
well,
|
||||
true,
|
||||
);
|
||||
cosmic::iced::widget::stack([
|
||||
card.into(),
|
||||
container(close)
|
||||
.align_right(Length::Fill)
|
||||
.padding([5, 6])
|
||||
.into(),
|
||||
])
|
||||
.into()
|
||||
} else {
|
||||
card.into()
|
||||
};
|
||||
let card: Element<'_, cosmic::Action<Message>> = card.into();
|
||||
let card: Element<'_, cosmic::Action<Message>> = if count > 1 {
|
||||
let height = popup_height(notice, hovered, count) as f32;
|
||||
let height = popup_height(notice, count) as f32;
|
||||
let back = move |inset: f32, offset: f32| {
|
||||
container(
|
||||
container(
|
||||
@@ -436,7 +590,11 @@ impl FereseShell {
|
||||
.on_enter(cosmic::Action::App(Message::HoverNotification(id, true)))
|
||||
.on_exit(cosmic::Action::App(Message::HoverNotification(id, false)));
|
||||
if count > 1 {
|
||||
area = area.on_press(cosmic::Action::App(Message::ToggleNotificationHistory));
|
||||
area = area.on_press(cosmic::Action::App(if history {
|
||||
Message::ToggleNotificationGroup(notice.app.clone())
|
||||
} else {
|
||||
Message::ToggleNotificationHistory
|
||||
}));
|
||||
} else if notice.live && notice.actions.iter().any(|(key, _)| key == "default") {
|
||||
area = area.on_press(cosmic::Action::App(Message::InvokeNotification(
|
||||
id,
|
||||
@@ -449,101 +607,277 @@ impl FereseShell {
|
||||
fn view_notifications(&self) -> Element<'_, cosmic::Action<Message>> {
|
||||
if self.notifications.history_open {
|
||||
let palette = self.config.theme;
|
||||
let surface = self.notification_surface.as_ref();
|
||||
let progress = surface.map_or(1.0, NotificationSurface::progress);
|
||||
let opacity = if surface
|
||||
.and_then(|surface| surface.effects.as_ref())
|
||||
.is_some_and(|effects| effects.surface.version() >= 3)
|
||||
{
|
||||
1.0
|
||||
} else {
|
||||
progress
|
||||
};
|
||||
let color = move |rgba| color_with_opacity(rgba, opacity);
|
||||
let primary = color(palette.text_primary);
|
||||
let muted = color(palette.text_muted);
|
||||
let accent = color(palette.accent);
|
||||
let base = color(palette.surface_base);
|
||||
let header = row([])
|
||||
.spacing(8)
|
||||
.align_y(alignment::Vertical::Center)
|
||||
let panel = blend(base, primary, 0.035);
|
||||
let well = blend(base, primary, 0.07);
|
||||
let mut bold = *SHELL_FONT.get().unwrap().read().unwrap();
|
||||
bold.weight = cosmic::iced::font::Weight::Bold;
|
||||
let groups = self.notifications.history_groups();
|
||||
let count = self.notifications.entries.len();
|
||||
let subtitle = if count == 0 {
|
||||
"A quiet moment".to_owned()
|
||||
} else {
|
||||
format!(
|
||||
"{count} {} · {} {}",
|
||||
if count == 1 {
|
||||
"notification"
|
||||
} else {
|
||||
"notifications"
|
||||
},
|
||||
groups.len(),
|
||||
if groups.len() == 1 { "app" } else { "apps" }
|
||||
)
|
||||
};
|
||||
let heading = column([])
|
||||
.spacing(3)
|
||||
.push(
|
||||
text("Notifications")
|
||||
.size(16)
|
||||
.class(theme::Text::Color(primary))
|
||||
.width(Length::Fill),
|
||||
.font(bold)
|
||||
.class(theme::Text::Color(primary)),
|
||||
)
|
||||
.push(motion::button(
|
||||
button::custom(text("Clear all").size(11))
|
||||
.on_press(cosmic::Action::App(Message::ClearNotifications))
|
||||
.padding([4, 6]),
|
||||
muted,
|
||||
false,
|
||||
1.0,
|
||||
))
|
||||
.push(card_button(
|
||||
container(text("×").size(14)).center_x(18).center_y(18),
|
||||
.push(text(subtitle).size(11).class(theme::Text::Color(muted)));
|
||||
let header = row([])
|
||||
.spacing(10)
|
||||
.align_y(alignment::Vertical::Center)
|
||||
.push(container(heading).width(Length::Fill))
|
||||
.push(icon_control(
|
||||
include_bytes!("../assets/icons/status/close.svg"),
|
||||
"Close notification center",
|
||||
Message::ToggleNotificationHistory,
|
||||
muted,
|
||||
blend(base, primary, 0.07),
|
||||
true,
|
||||
well,
|
||||
28,
|
||||
));
|
||||
let dnd_color = if self.notifications.dnd {
|
||||
accent
|
||||
} else {
|
||||
muted
|
||||
};
|
||||
let dnd_icon = if self.notifications.dnd {
|
||||
include_bytes!("../assets/icons/status/notifications-off.svg").as_slice()
|
||||
} else {
|
||||
include_bytes!("../assets/icons/status/notifications.svg").as_slice()
|
||||
};
|
||||
let dnd = row([])
|
||||
.spacing(10)
|
||||
.align_y(alignment::Vertical::Center)
|
||||
.push(accented_icon(dnd_icon, 20, dnd_color, dnd_color))
|
||||
.push(
|
||||
text("Do Not Disturb")
|
||||
.size(12)
|
||||
.class(theme::Text::Color(muted))
|
||||
.width(Length::Fill),
|
||||
container(
|
||||
column([])
|
||||
.spacing(2)
|
||||
.push(
|
||||
text("Do Not Disturb")
|
||||
.size(12)
|
||||
.font(bold)
|
||||
.class(theme::Text::Color(primary)),
|
||||
)
|
||||
.push(
|
||||
text(if self.notifications.dnd {
|
||||
"Popups paused"
|
||||
} else {
|
||||
"Popups enabled"
|
||||
})
|
||||
.size(11)
|
||||
.class(theme::Text::Color(muted)),
|
||||
),
|
||||
)
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.push(motion::button(
|
||||
button::custom(
|
||||
text(if self.notifications.dnd { "On" } else { "Off" }).size(12),
|
||||
container(
|
||||
text(if self.notifications.dnd { "On" } else { "Off" })
|
||||
.size(12)
|
||||
.font(bold)
|
||||
.class(theme::Text::Color(dnd_color)),
|
||||
)
|
||||
.center_x(40)
|
||||
.center_y(26),
|
||||
)
|
||||
.on_press(cosmic::Action::App(Message::Control(status::Action::Dnd(
|
||||
!self.notifications.dnd,
|
||||
))))
|
||||
.padding([4, 8]),
|
||||
if self.notifications.dnd {
|
||||
accent
|
||||
} else {
|
||||
muted
|
||||
},
|
||||
.padding(0),
|
||||
dnd_color,
|
||||
self.notifications.dnd,
|
||||
1.0,
|
||||
));
|
||||
let controls = container(column([]).spacing(8).push(header).push(dnd))
|
||||
.padding(14)
|
||||
.width(Length::Fill)
|
||||
.class(theme::Container::custom(move |_| container::Style {
|
||||
background: Some(Background::Color(base)),
|
||||
border: Border {
|
||||
radius: palette.material_radius.into(),
|
||||
let controls =
|
||||
container(dnd)
|
||||
.padding(10)
|
||||
.width(Length::Fill)
|
||||
.class(theme::Container::custom(move |_| container::Style {
|
||||
background: Some(Background::Color(base)),
|
||||
border: Border {
|
||||
radius: palette.material_radius.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}));
|
||||
let mut entries = column([]).spacing(8);
|
||||
if self.notifications.entries.is_empty() {
|
||||
entries = entries.push(
|
||||
container(
|
||||
text("You're all caught up")
|
||||
.size(14)
|
||||
.class(theme::Text::Color(muted)),
|
||||
}));
|
||||
let mut content = column([]).spacing(10).push(header).push(controls);
|
||||
if count == 0 {
|
||||
let empty = column([])
|
||||
.spacing(10)
|
||||
.align_x(alignment::Horizontal::Center)
|
||||
.push(
|
||||
container(accented_icon(
|
||||
include_bytes!("../assets/icons/status/notifications.svg"),
|
||||
26,
|
||||
muted,
|
||||
accent,
|
||||
))
|
||||
.center_x(56)
|
||||
.center_y(56)
|
||||
.class(theme::Container::custom(move |_| container::Style {
|
||||
background: Some(Background::Color(well)),
|
||||
border: Border {
|
||||
radius: 28.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})),
|
||||
)
|
||||
.padding(32)
|
||||
.center_x(Length::Fill),
|
||||
.push(
|
||||
text("You're all caught up")
|
||||
.size(15)
|
||||
.font(bold)
|
||||
.class(theme::Text::Color(primary)),
|
||||
)
|
||||
.push(
|
||||
text("New notifications will appear here.")
|
||||
.size(11)
|
||||
.class(theme::Text::Color(muted)),
|
||||
);
|
||||
content = content.push(
|
||||
container(empty)
|
||||
.center_x(Length::Fill)
|
||||
.center_y(Length::Fill),
|
||||
);
|
||||
} else {
|
||||
content = content.push(
|
||||
row([])
|
||||
.align_y(alignment::Vertical::Center)
|
||||
.push(
|
||||
text("Recent")
|
||||
.size(11)
|
||||
.font(bold)
|
||||
.class(theme::Text::Color(muted))
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.push(icon_control(
|
||||
include_bytes!("../assets/icons/status/trash.svg"),
|
||||
"Clear all notifications",
|
||||
Message::ClearNotifications,
|
||||
muted,
|
||||
well,
|
||||
26,
|
||||
)),
|
||||
);
|
||||
let mut entries = column([]).spacing(8);
|
||||
for group in groups {
|
||||
let latest = group[0];
|
||||
if group.len() > 1 && self.notifications.expanded_apps.contains(&latest.app) {
|
||||
let app = if latest.app.is_empty() {
|
||||
"Notifications"
|
||||
} else {
|
||||
&latest.app
|
||||
};
|
||||
let group_header = row([])
|
||||
.spacing(6)
|
||||
.align_y(alignment::Vertical::Center)
|
||||
.push(
|
||||
text(format!("{} · {}", app, group.len()))
|
||||
.size(11)
|
||||
.font(bold)
|
||||
.class(theme::Text::Color(muted))
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.push(icon_control(
|
||||
include_bytes!("../assets/icons/status/chevron-up.svg"),
|
||||
"Collapse group",
|
||||
Message::ToggleNotificationGroup(latest.app.clone()),
|
||||
muted,
|
||||
well,
|
||||
24,
|
||||
))
|
||||
.push(icon_control(
|
||||
include_bytes!("../assets/icons/status/trash.svg"),
|
||||
"Clear group",
|
||||
Message::RemoveNotificationGroup(latest.app.clone()),
|
||||
muted,
|
||||
well,
|
||||
24,
|
||||
));
|
||||
let mut cards = column([]).spacing(8).push(group_header);
|
||||
for notice in group {
|
||||
cards = cards.push(self.notification_card(notice, opacity, true, 1));
|
||||
}
|
||||
entries = entries.push(cards);
|
||||
} else {
|
||||
entries = entries.push(self.notification_card(
|
||||
latest,
|
||||
opacity,
|
||||
true,
|
||||
group.len(),
|
||||
));
|
||||
}
|
||||
}
|
||||
content = content.push(
|
||||
scrollable(entries)
|
||||
.direction(Direction::Vertical(Scrollbar::hidden()))
|
||||
.height(Length::Fill),
|
||||
);
|
||||
}
|
||||
for notice in self.notifications.entries.iter().rev() {
|
||||
entries = entries.push(self.notification_card(notice, 1.0, true, 1));
|
||||
}
|
||||
let content = column([])
|
||||
.spacing(8)
|
||||
.push(controls)
|
||||
.push(scrollable(entries).height(Length::Fill));
|
||||
container(content)
|
||||
.padding(4)
|
||||
let panel = container(content)
|
||||
.padding(14)
|
||||
.height(Length::Fill)
|
||||
.width(Length::Fill)
|
||||
.into()
|
||||
.class(theme::Container::custom(move |_| container::Style {
|
||||
background: Some(Background::Color(panel)),
|
||||
border: Border {
|
||||
color: color(palette.border),
|
||||
width: 1.0,
|
||||
radius: palette.material_radius.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.into();
|
||||
motion::animated(
|
||||
panel,
|
||||
progress,
|
||||
surface
|
||||
.map(|surface| surface.regions.clone())
|
||||
.unwrap_or_default(),
|
||||
palette.material_radius,
|
||||
)
|
||||
} else {
|
||||
let mut cards = column([]).spacing(8);
|
||||
for (notice, opacity, count) in self.notifications.popup_groups() {
|
||||
cards = cards.push(self.notification_card(notice, opacity, false, count));
|
||||
}
|
||||
container(scrollable(cards).height(Length::Fill))
|
||||
.padding(4)
|
||||
.into()
|
||||
container(
|
||||
scrollable(cards)
|
||||
.direction(Direction::Vertical(Scrollbar::hidden()))
|
||||
.height(Length::Fill),
|
||||
)
|
||||
.padding(4)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user