Unify notification center popovers and translucent card materials
This commit is contained in:
1 parent
3c532725ec
commit
23d84c0a77
4 files changed
+159
-170
No files matched your search
@@ -3,7 +3,10 @@
|
|||||||
Ferese Shell provides themed notification cards and an in-session history panel.
|
Ferese Shell provides themed notification cards and an in-session history panel.
|
||||||
Cards appear beneath the bar on the focused output and follow the selected theme.
|
Cards appear beneath the bar on the focused output and follow the selected theme.
|
||||||
Click the notification icon in the bar to open history, toggle Do Not Disturb, or
|
Click the notification icon in the bar to open history, toggle Do Not Disturb, or
|
||||||
clear notifications. Escape or the close button closes the history panel.
|
clear notifications. Escape, clicking outside, or the close button closes the history panel.
|
||||||
|
The center uses the same anchored popup and animation as the other bar menus.
|
||||||
|
Both the center and popup cards follow the shell material: translucent mode uses
|
||||||
|
the compositor’s blur and tint, while solid mode stays opaque.
|
||||||
|
|
||||||
## Send a notification
|
## Send a notification
|
||||||
|
|
||||||
|
|||||||
@@ -400,10 +400,6 @@ impl cosmic::Application for FereseShell {
|
|||||||
.menu
|
.menu
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|menu| menu.animating() || menu.motion.closing())
|
.is_some_and(|menu| menu.animating() || menu.motion.closing())
|
||||||
|| self
|
|
||||||
.notification_surface
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|surface| surface.animating())
|
|
||||||
{
|
{
|
||||||
cosmic::iced::time::every(Duration::from_millis(16)).map(|_| Message::AnimateMenu)
|
cosmic::iced::time::every(Duration::from_millis(16)).map(|_| Message::AnimateMenu)
|
||||||
} else {
|
} else {
|
||||||
@@ -424,6 +420,14 @@ impl cosmic::Application for FereseShell {
|
|||||||
eprintln!("ferese-shell: could not update card materials: {error}");
|
eprintln!("ferese-shell: could not update card materials: {error}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(surface) = &self.notification_surface
|
||||||
|
&& let Some(effects) = &surface.effects
|
||||||
|
{
|
||||||
|
let regions = surface.regions.lock().unwrap().clone();
|
||||||
|
if let Err(error) = effects.set_regions(®ions) {
|
||||||
|
eprintln!("ferese-shell: could not update notification materials: {error}");
|
||||||
|
}
|
||||||
|
}
|
||||||
match message {
|
match message {
|
||||||
Message::BeginNoteEdit(id) => {
|
Message::BeginNoteEdit(id) => {
|
||||||
let mut tasks = vec![self.finish_note_edit()];
|
let mut tasks = vec![self.finish_note_edit()];
|
||||||
@@ -509,7 +513,7 @@ impl cosmic::Application for FereseShell {
|
|||||||
&& entry.id == id
|
&& entry.id == id
|
||||||
&& entry.effects.is_none()
|
&& entry.effects.is_none()
|
||||||
{
|
{
|
||||||
match EffectsBinding::attach_role(&surface, None, entry.progress()) {
|
match EffectsBinding::attach_role(&surface, None, 1.0) {
|
||||||
Ok(binding) => entry.effects = Some(binding),
|
Ok(binding) => entry.effects = Some(binding),
|
||||||
Err(error) => eprintln!(
|
Err(error) => eprintln!(
|
||||||
"ferese-shell: notification effects unavailable: {error}"
|
"ferese-shell: notification effects unavailable: {error}"
|
||||||
@@ -596,27 +600,17 @@ impl cosmic::Application for FereseShell {
|
|||||||
self.notifications.clear();
|
self.notifications.clear();
|
||||||
self.sync_notification_surface()
|
self.sync_notification_surface()
|
||||||
}
|
}
|
||||||
Message::AnimateMenu => {
|
Message::AnimateMenu => self.animate_menu(),
|
||||||
Task::batch([self.animate_menu(), self.animate_notification_history()])
|
|
||||||
}
|
|
||||||
Message::OpenMenu(kind, anchor) => self.open_menu(kind, anchor),
|
Message::OpenMenu(kind, anchor) => self.open_menu(kind, anchor),
|
||||||
Message::OpenMenuOn(id, kind, anchor) => {
|
Message::OpenMenuOn(id, kind, anchor) => {
|
||||||
if kind == status_ui::Menu::Notifications && self.notifications.ready {
|
|
||||||
return Task::batch([self.destroy_menu(), self.toggle_notification_history()]);
|
|
||||||
}
|
|
||||||
let hide_history = if self.notifications.history_open {
|
|
||||||
self.close_notification_history()
|
|
||||||
} else {
|
|
||||||
Task::none()
|
|
||||||
};
|
|
||||||
if self.bar_surface_id != id {
|
if self.bar_surface_id != id {
|
||||||
let destroy = self.destroy_menu();
|
let destroy = self.destroy_menu();
|
||||||
self.bar_surface_id = id;
|
self.bar_surface_id = id;
|
||||||
let open = self.open_menu(kind, anchor);
|
let open = self.open_menu(kind, anchor);
|
||||||
Task::batch([hide_history, destroy, open])
|
Task::batch([destroy, open])
|
||||||
} else {
|
} else {
|
||||||
let open = self.open_menu(kind, anchor);
|
let open = self.open_menu(kind, anchor);
|
||||||
Task::batch([hide_history, open])
|
open
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::ConfirmPower(action) => {
|
Message::ConfirmPower(action) => {
|
||||||
@@ -777,14 +771,6 @@ impl FereseShell {
|
|||||||
{
|
{
|
||||||
menu.motion.update_settings(config.animations);
|
menu.motion.update_settings(config.animations);
|
||||||
}
|
}
|
||||||
if self.config.animations != config.animations
|
|
||||||
&& let Some(motion) = self
|
|
||||||
.notification_surface
|
|
||||||
.as_mut()
|
|
||||||
.and_then(|surface| surface.motion.as_mut())
|
|
||||||
{
|
|
||||||
motion.update_settings(config.animations);
|
|
||||||
}
|
|
||||||
self.status_service
|
self.status_service
|
||||||
.update_settings(config.status.settings_command.clone());
|
.update_settings(config.status.settings_command.clone());
|
||||||
let old = self.config.theme;
|
let old = self.config.theme;
|
||||||
@@ -1786,14 +1772,6 @@ impl FereseShell {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|surface| surface.id == id) =>
|
.is_some_and(|surface| surface.id == id) =>
|
||||||
{
|
{
|
||||||
if let Some(surface) = &mut self.notification_surface
|
|
||||||
&& surface.id == id
|
|
||||||
{
|
|
||||||
surface.ready = true;
|
|
||||||
if let Some(motion) = &mut surface.motion {
|
|
||||||
motion.begin(Instant::now());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Iced emits the first xdg_popup configure as Window::Opened,
|
// Iced emits the first xdg_popup configure as Window::Opened,
|
||||||
// not Popup::Configured. This is the popup's ready signal.
|
// not Popup::Configured. This is the popup's ready signal.
|
||||||
if let Some(menu) = &mut self.menu
|
if let Some(menu) = &mut self.menu
|
||||||
@@ -1821,9 +1799,13 @@ impl FereseShell {
|
|||||||
{
|
{
|
||||||
match event {
|
match event {
|
||||||
wayland::PopupEvent::Done => {
|
wayland::PopupEvent::Done => {
|
||||||
|
if menu.kind == status_ui::Menu::Notifications {
|
||||||
|
self.notifications.history_open = false;
|
||||||
|
self.notifications.hovered = None;
|
||||||
|
}
|
||||||
self.menu = None;
|
self.menu = None;
|
||||||
EFFECT_FRAME_PENDING.store(false, Ordering::Relaxed);
|
EFFECT_FRAME_PENDING.store(false, Ordering::Relaxed);
|
||||||
return Task::none();
|
return self.sync_notification_surface();
|
||||||
}
|
}
|
||||||
wayland::PopupEvent::Focused => {
|
wayland::PopupEvent::Focused => {
|
||||||
if menu.effects.is_none() {
|
if menu.effects.is_none() {
|
||||||
@@ -1912,12 +1894,8 @@ impl FereseShell {
|
|||||||
.is_some_and(|entry| entry.id == layer_id) =>
|
.is_some_and(|entry| entry.id == layer_id) =>
|
||||||
{
|
{
|
||||||
let entry = self.notification_surface.as_mut().unwrap();
|
let entry = self.notification_surface.as_mut().unwrap();
|
||||||
entry.ready = true;
|
|
||||||
if let Some(motion) = &mut entry.motion {
|
|
||||||
motion.begin(Instant::now());
|
|
||||||
}
|
|
||||||
if entry.effects.is_none() {
|
if entry.effects.is_none() {
|
||||||
match EffectsBinding::attach_role(&surface, None, entry.progress()) {
|
match EffectsBinding::attach_role(&surface, None, 1.0) {
|
||||||
Ok(binding) => entry.effects = Some(binding),
|
Ok(binding) => entry.effects = Some(binding),
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
eprintln!("ferese-shell: notification effects unavailable: {error}")
|
eprintln!("ferese-shell: notification effects unavailable: {error}")
|
||||||
|
|||||||
@@ -154,76 +154,44 @@ pub(super) struct NotificationSurface {
|
|||||||
pub id: window::Id,
|
pub id: window::Id,
|
||||||
output: wl_output::WlOutput,
|
output: wl_output::WlOutput,
|
||||||
height: u32,
|
height: u32,
|
||||||
pub motion: Option<motion::PopupMotion>,
|
|
||||||
pub effects: Option<EffectsBinding>,
|
pub effects: Option<EffectsBinding>,
|
||||||
pub ready: bool,
|
pub regions: motion::Regions,
|
||||||
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 {
|
impl FereseShell {
|
||||||
pub(super) fn toggle_notification_history(&mut self) -> Task<Message> {
|
pub(super) fn toggle_notification_history(&mut self) -> Task<Message> {
|
||||||
if self.notifications.history_open {
|
if self.notifications.history_open {
|
||||||
if let Some(motion) = self
|
return self.close_menu();
|
||||||
.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();
|
// Cards can open history too. Anchor those requests to the active bar;
|
||||||
self.sync_notification_surface()
|
// bell clicks pass their exact anchor through the normal menu path.
|
||||||
|
if let Some(output) = self.outputs.iter().find(|entry| {
|
||||||
|
self.snapshot
|
||||||
|
.outputs
|
||||||
|
.iter()
|
||||||
|
.any(|output| output.focused && Some(output.name.as_str()) == entry.name.as_deref())
|
||||||
|
}) {
|
||||||
|
self.bar_surface_id = output.bar;
|
||||||
|
}
|
||||||
|
let width = self
|
||||||
|
.outputs
|
||||||
|
.iter()
|
||||||
|
.find(|entry| entry.bar == self.bar_surface_id)
|
||||||
|
.and_then(|entry| entry.size)
|
||||||
|
.map_or(POPUP_WIDTH as i32, |size| size.0);
|
||||||
|
self.open_menu(
|
||||||
|
status_ui::Menu::Notifications,
|
||||||
|
cosmic::iced::Rectangle {
|
||||||
|
x: (width - 48).max(0),
|
||||||
|
y: 0,
|
||||||
|
width: 24,
|
||||||
|
height: self.config.theme.bar_height.round() as i32,
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn close_notification_history(&mut self) -> Task<Message> {
|
pub(super) fn close_notification_history(&mut self) -> Task<Message> {
|
||||||
if let Some(motion) = self
|
self.close_menu()
|
||||||
.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> {
|
pub(super) fn sync_notification_surface(&mut self) -> Task<Message> {
|
||||||
@@ -234,7 +202,7 @@ impl FereseShell {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
let count = self.notifications.popup_groups().len();
|
let count = self.notifications.popup_groups().len();
|
||||||
let wanted = self.notifications.ready && (self.notifications.history_open || count > 0);
|
let wanted = self.notifications.ready && !self.notifications.history_open && count > 0;
|
||||||
let selected = self
|
let selected = self
|
||||||
.outputs
|
.outputs
|
||||||
.iter()
|
.iter()
|
||||||
@@ -260,19 +228,16 @@ impl FereseShell {
|
|||||||
let Some(output) = selected else {
|
let Some(output) = selected else {
|
||||||
return Task::batch(tasks);
|
return Task::batch(tasks);
|
||||||
};
|
};
|
||||||
let desired_height = if self.notifications.history_open {
|
let desired_height = self
|
||||||
history_height(&self.notifications)
|
.notifications
|
||||||
} else {
|
.popup_groups()
|
||||||
self.notifications
|
.iter()
|
||||||
.popup_groups()
|
.map(|(notice, _, count)| {
|
||||||
.iter()
|
popup_height(notice, *count) + if *count > 1 { 12 } else { 0 }
|
||||||
.map(|(notice, _, count)| {
|
})
|
||||||
popup_height(notice, *count) + if *count > 1 { 12 } else { 0 }
|
.sum::<u32>()
|
||||||
})
|
+ count.saturating_sub(1) as u32 * 8
|
||||||
.sum::<u32>()
|
+ 8;
|
||||||
+ count.saturating_sub(1) as u32 * 8
|
|
||||||
+ 8
|
|
||||||
};
|
|
||||||
let output_height = self
|
let output_height = self
|
||||||
.outputs
|
.outputs
|
||||||
.iter()
|
.iter()
|
||||||
@@ -292,9 +257,7 @@ impl FereseShell {
|
|||||||
id,
|
id,
|
||||||
output: output.clone(),
|
output: output.clone(),
|
||||||
height,
|
height,
|
||||||
motion: None,
|
|
||||||
effects: None,
|
effects: None,
|
||||||
ready: false,
|
|
||||||
regions: Default::default(),
|
regions: Default::default(),
|
||||||
});
|
});
|
||||||
let top = self.config.theme.bar_height + self.config.theme.bar_margin_top as f32 + 12.0;
|
let top = self.config.theme.bar_height + self.config.theme.bar_margin_top as f32 + 12.0;
|
||||||
@@ -321,19 +284,6 @@ impl FereseShell {
|
|||||||
);
|
);
|
||||||
tasks.push(cosmic::task::message(cosmic::Action::Surface(action)));
|
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)
|
Task::batch(tasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,7 +303,16 @@ impl FereseShell {
|
|||||||
let foreground = tint(palette.text_primary);
|
let foreground = tint(palette.text_primary);
|
||||||
let muted = tint(palette.text_muted);
|
let muted = tint(palette.text_muted);
|
||||||
let accent = tint(palette.accent);
|
let accent = tint(palette.accent);
|
||||||
let base = tint(palette.surface_base);
|
let base = tint(if history {
|
||||||
|
palette.surface_base
|
||||||
|
} else {
|
||||||
|
palette.surface_popover
|
||||||
|
});
|
||||||
|
let compositor_material = !history
|
||||||
|
&& self
|
||||||
|
.notification_surface
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|surface| surface.effects.is_some());
|
||||||
let divider = tint(palette.border);
|
let divider = tint(palette.border);
|
||||||
let well = blend(base, foreground, 0.07);
|
let well = blend(base, foreground, 0.07);
|
||||||
let id = notice.id;
|
let id = notice.id;
|
||||||
@@ -543,11 +502,25 @@ impl FereseShell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let card = container(face)
|
let card = container(face)
|
||||||
|
.id(if history {
|
||||||
|
"ferese-history-card"
|
||||||
|
} else {
|
||||||
|
"ferese-blur-card"
|
||||||
|
})
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.clip(true)
|
.clip(true)
|
||||||
.class(theme::Container::custom(move |_| container::Style {
|
.class(theme::Container::custom(move |_| container::Style {
|
||||||
background: Some(Background::Color(base)),
|
background: if history {
|
||||||
|
Some(Background::Color(Color {
|
||||||
|
a: 0.045 * opacity,
|
||||||
|
..foreground
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
(!compositor_material).then_some(Background::Color(base))
|
||||||
|
},
|
||||||
border: Border {
|
border: Border {
|
||||||
|
color: color_with_opacity(palette.text_muted, 0.18 * opacity),
|
||||||
|
width: if history { 0.0 } else { 1.0 },
|
||||||
radius: palette.material_radius.into(),
|
radius: palette.material_radius.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -555,34 +528,32 @@ impl FereseShell {
|
|||||||
}));
|
}));
|
||||||
let card: Element<'_, cosmic::Action<Message>> = card.into();
|
let card: Element<'_, cosmic::Action<Message>> = card.into();
|
||||||
let card: Element<'_, cosmic::Action<Message>> = if count > 1 {
|
let card: Element<'_, cosmic::Action<Message>> = if count > 1 {
|
||||||
let height = popup_height(notice, count) as f32;
|
// With translucent materials, complete backing rectangles show
|
||||||
let back = move |inset: f32, offset: f32| {
|
// through the face and multiply its tint. Paint only the exposed
|
||||||
|
// bottom edges, after the card's measured height.
|
||||||
|
let back = move |inset: f32| {
|
||||||
container(
|
container(
|
||||||
container(
|
container(
|
||||||
cosmic::iced::widget::Space::new()
|
cosmic::iced::widget::Space::new()
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(height),
|
.height(6),
|
||||||
)
|
)
|
||||||
.class(theme::Container::custom(move |_| {
|
.width(Length::Fill)
|
||||||
container::Style {
|
.class(theme::Container::custom(move |_| container::Style {
|
||||||
background: Some(Background::Color(blend(base, foreground, 0.025))),
|
background: Some(Background::Color(Color {
|
||||||
border: Border {
|
a: 0.045 * opacity,
|
||||||
radius: palette.material_radius.into(),
|
..foreground
|
||||||
..Default::default()
|
})),
|
||||||
},
|
border: Border {
|
||||||
|
radius: [0.0, 0.0, motion::radius(6.0), motion::radius(6.0)].into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
},
|
||||||
|
..Default::default()
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
.padding(cosmic::iced::Padding {
|
.padding([0.0, inset])
|
||||||
top: offset,
|
|
||||||
left: inset,
|
|
||||||
right: inset,
|
|
||||||
bottom: 0.0,
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
cosmic::iced::widget::stack([back(12.0, 12.0).into(), back(6.0, 6.0).into(), card])
|
column([card, back(6.0).into(), back(12.0).into()]).into()
|
||||||
.into()
|
|
||||||
} else {
|
} else {
|
||||||
card
|
card
|
||||||
};
|
};
|
||||||
@@ -604,11 +575,11 @@ impl FereseShell {
|
|||||||
area.into()
|
area.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view_notifications(&self) -> Element<'_, cosmic::Action<Message>> {
|
pub(super) fn view_notifications(&self) -> Element<'_, cosmic::Action<Message>> {
|
||||||
if self.notifications.history_open {
|
if self.notifications.history_open {
|
||||||
let palette = self.config.theme;
|
let palette = self.config.theme;
|
||||||
let surface = self.notification_surface.as_ref();
|
let surface = self.menu.as_ref();
|
||||||
let progress = surface.map_or(1.0, NotificationSurface::progress);
|
let progress = surface.map_or(1.0, status_ui::OpenMenu::progress);
|
||||||
let opacity = if surface
|
let opacity = if surface
|
||||||
.and_then(|surface| surface.effects.as_ref())
|
.and_then(|surface| surface.effects.as_ref())
|
||||||
.is_some_and(|effects| effects.surface.version() >= 3)
|
.is_some_and(|effects| effects.surface.version() >= 3)
|
||||||
@@ -622,7 +593,7 @@ impl FereseShell {
|
|||||||
let muted = color(palette.text_muted);
|
let muted = color(palette.text_muted);
|
||||||
let accent = color(palette.accent);
|
let accent = color(palette.accent);
|
||||||
let base = color(palette.surface_base);
|
let base = color(palette.surface_base);
|
||||||
let panel = blend(base, primary, 0.035);
|
let compositor_material = surface.is_some_and(|menu| menu.effects.is_some());
|
||||||
let well = blend(base, primary, 0.07);
|
let well = blend(base, primary, 0.07);
|
||||||
let mut bold = *SHELL_FONT.get().unwrap().read().unwrap();
|
let mut bold = *SHELL_FONT.get().unwrap().read().unwrap();
|
||||||
bold.weight = cosmic::iced::font::Weight::Bold;
|
bold.weight = cosmic::iced::font::Weight::Bold;
|
||||||
@@ -845,13 +816,15 @@ impl FereseShell {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
let panel = container(content)
|
let panel = container(content)
|
||||||
.padding(14)
|
.id("ferese-blur-card")
|
||||||
.height(Length::Fill)
|
.padding(16)
|
||||||
|
.height(history_height(&self.notifications) as f32)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.class(theme::Container::custom(move |_| container::Style {
|
.class(theme::Container::custom(move |_| container::Style {
|
||||||
background: Some(Background::Color(panel)),
|
background: (!compositor_material)
|
||||||
|
.then_some(Background::Color(color(palette.surface_popover))),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: color(palette.border),
|
color: color_with_opacity(palette.text_muted, 0.18 * opacity),
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: palette.material_radius.into(),
|
radius: palette.material_radius.into(),
|
||||||
},
|
},
|
||||||
@@ -871,13 +844,21 @@ impl FereseShell {
|
|||||||
for (notice, opacity, count) in self.notifications.popup_groups() {
|
for (notice, opacity, count) in self.notifications.popup_groups() {
|
||||||
cards = cards.push(self.notification_card(notice, opacity, false, count));
|
cards = cards.push(self.notification_card(notice, opacity, false, count));
|
||||||
}
|
}
|
||||||
container(
|
motion::animated(
|
||||||
scrollable(cards)
|
container(
|
||||||
.direction(Direction::Vertical(Scrollbar::hidden()))
|
scrollable(cards)
|
||||||
.height(Length::Fill),
|
.direction(Direction::Vertical(Scrollbar::hidden()))
|
||||||
|
.height(Length::Fill),
|
||||||
|
)
|
||||||
|
.padding(4)
|
||||||
|
.into(),
|
||||||
|
1.0,
|
||||||
|
self.notification_surface
|
||||||
|
.as_ref()
|
||||||
|
.map(|surface| surface.regions.clone())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
self.config.theme.material_radius,
|
||||||
)
|
)
|
||||||
.padding(4)
|
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,6 +25,7 @@ impl Menu {
|
|||||||
match self {
|
match self {
|
||||||
Self::System => 360.0,
|
Self::System => 360.0,
|
||||||
Self::Battery => 280.0,
|
Self::Battery => 280.0,
|
||||||
|
Self::Notifications => 368.0,
|
||||||
_ => 300.0,
|
_ => 300.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,6 +91,10 @@ impl FereseShell {
|
|||||||
if !kind.available(&self.status) {
|
if !kind.available(&self.status) {
|
||||||
return destroy;
|
return destroy;
|
||||||
}
|
}
|
||||||
|
if kind == Menu::Notifications && self.notifications.ready {
|
||||||
|
self.notifications.toggle_history();
|
||||||
|
}
|
||||||
|
let notifications = self.sync_notification_surface();
|
||||||
let id = window::Id::unique();
|
let id = window::Id::unique();
|
||||||
self.menu = Some(OpenMenu {
|
self.menu = Some(OpenMenu {
|
||||||
id,
|
id,
|
||||||
@@ -149,16 +154,25 @@ impl FereseShell {
|
|||||||
},
|
},
|
||||||
Some(Box::new(Self::view_status_menu)),
|
Some(Box::new(Self::view_status_menu)),
|
||||||
);
|
);
|
||||||
destroy.chain(cosmic::task::message(cosmic::Action::Surface(action)))
|
Task::batch([destroy, notifications])
|
||||||
|
.chain(cosmic::task::message(cosmic::Action::Surface(action)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy_menu(&mut self) -> Task<Message> {
|
pub fn destroy_menu(&mut self) -> Task<Message> {
|
||||||
super::EFFECT_FRAME_PENDING.store(false, std::sync::atomic::Ordering::Relaxed);
|
super::EFFECT_FRAME_PENDING.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||||
self.menu.take().map_or_else(Task::none, |menu| {
|
let Some(menu) = self.menu.take() else {
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
if menu.kind == Menu::Notifications {
|
||||||
|
self.notifications.history_open = false;
|
||||||
|
self.notifications.hovered = None;
|
||||||
|
}
|
||||||
|
Task::batch([
|
||||||
cosmic::task::message(cosmic::Action::Surface(
|
cosmic::task::message(cosmic::Action::Surface(
|
||||||
cosmic::surface::action::destroy_popup(menu.id),
|
cosmic::surface::action::destroy_popup(menu.id),
|
||||||
))
|
)),
|
||||||
})
|
self.sync_notification_surface(),
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close_menu(&mut self) -> Task<Message> {
|
pub fn close_menu(&mut self) -> Task<Message> {
|
||||||
@@ -320,6 +334,19 @@ impl FereseShell {
|
|||||||
let Some(menu) = &self.menu else {
|
let Some(menu) = &self.menu else {
|
||||||
return text("").into();
|
return text("").into();
|
||||||
};
|
};
|
||||||
|
if menu.kind == Menu::Notifications && self.notifications.ready {
|
||||||
|
return cosmic::widget::autosize::autosize(
|
||||||
|
self.view_notifications(),
|
||||||
|
cosmic::iced::advanced::widget::Id::new("ferese-notification-center"),
|
||||||
|
)
|
||||||
|
.limits(
|
||||||
|
Limits::NONE
|
||||||
|
.min_width(menu.kind.width())
|
||||||
|
.max_width(menu.kind.width())
|
||||||
|
.max_height(720.0),
|
||||||
|
)
|
||||||
|
.into();
|
||||||
|
}
|
||||||
let theme = self.config.theme;
|
let theme = self.config.theme;
|
||||||
let p = if menu.effects.is_some() {
|
let p = if menu.effects.is_some() {
|
||||||
1.0
|
1.0
|
||||||
@@ -1182,7 +1209,7 @@ mod tests {
|
|||||||
assert_eq!(Menu::Bluetooth.width(), 300.0);
|
assert_eq!(Menu::Bluetooth.width(), 300.0);
|
||||||
assert_eq!(Menu::Battery.width(), 280.0);
|
assert_eq!(Menu::Battery.width(), 280.0);
|
||||||
assert_eq!(Menu::Audio.width(), 300.0);
|
assert_eq!(Menu::Audio.width(), 300.0);
|
||||||
assert_eq!(Menu::Notifications.width(), 300.0);
|
assert_eq!(Menu::Notifications.width(), 368.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in new issue
Block a user