Refine Pomodoro popup styling and controls
This commit is contained in:
1 parent
97bbf1b866
commit
4661f595f8
3 files changed
+307
-98
No files matched your search
@@ -1,5 +1,6 @@
|
|||||||
//! Shared Activities item and Morph presentation. Publishers provide data only.
|
//! Shared Activities item and Morph presentation. Publishers provide data only.
|
||||||
mod compact;
|
mod compact;
|
||||||
|
mod pomodoro;
|
||||||
mod ring;
|
mod ring;
|
||||||
pub(crate) mod subscription;
|
pub(crate) mod subscription;
|
||||||
use crate::{FereseShell, Message, status_ui};
|
use crate::{FereseShell, Message, status_ui};
|
||||||
@@ -949,60 +950,6 @@ impl FereseShell {
|
|||||||
.width(if intrinsic { Length::Fixed(budget) } else { Length::Fill })
|
.width(if intrinsic { Length::Fixed(budget) } else { Length::Fill })
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
fn pomodoro_controls(&self, opacity: f32) -> Element<'_, cosmic::Action<Message>> {
|
|
||||||
use ferese_activities::pomodoro::Status;
|
|
||||||
use ferese_ipc::activities::PomodoroAction as Action;
|
|
||||||
let current = self.activities.snapshot.pomodoro.as_ref();
|
|
||||||
let session = current.map(|current| current.id.clone());
|
|
||||||
let control = |label: &'static str, action| {
|
|
||||||
button::text(label).on_press(cosmic::Action::App(Message::Pomodoro(action, session.clone())))
|
|
||||||
};
|
|
||||||
let mut controls = row::with_capacity(4).spacing(6).align_y(Alignment::Center);
|
|
||||||
let description = if let Some(current) = current {
|
|
||||||
let timer = ¤t.timer;
|
|
||||||
if timer.status == Status::Finished {
|
|
||||||
controls = controls
|
|
||||||
.push(control("Start again", Action::Start))
|
|
||||||
.push(control("Dismiss", Action::Stop));
|
|
||||||
format!("Pomodoro finished · {} work intervals", timer.completed_work)
|
|
||||||
} else {
|
|
||||||
controls = controls.push(match timer.status {
|
|
||||||
Status::Running => control("Pause", Action::Pause),
|
|
||||||
Status::Paused => control("Resume", Action::Resume),
|
|
||||||
_ => control("Start phase", Action::Resume),
|
|
||||||
});
|
|
||||||
if timer.status != Status::Ready {
|
|
||||||
controls = controls.push(control("Skip", Action::Next));
|
|
||||||
}
|
|
||||||
controls = controls
|
|
||||||
.push(control("Reset", Action::Reset))
|
|
||||||
.push(control("Stop", Action::Stop));
|
|
||||||
format!(
|
|
||||||
"Pomodoro · {}{} · {}/{}",
|
|
||||||
timer.phase.label(),
|
|
||||||
match timer.status {
|
|
||||||
Status::Paused => " · Paused",
|
|
||||||
Status::Ready => " · Ready",
|
|
||||||
_ => "",
|
|
||||||
},
|
|
||||||
timer.completed_work,
|
|
||||||
timer.settings.intervals
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
controls = controls.push(control("Start Pomodoro", Action::Start));
|
|
||||||
"Pomodoro".into()
|
|
||||||
};
|
|
||||||
column![
|
|
||||||
self.render.text(description).size(12).class(theme::Text::Color(
|
|
||||||
self.config.theme.palette().text.scale_alpha(opacity)
|
|
||||||
)),
|
|
||||||
controls
|
|
||||||
]
|
|
||||||
.spacing(6)
|
|
||||||
.width(Length::Fill)
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
pub fn activity_list(&self, id: window::Id, opacity: f32) -> Element<'_, cosmic::Action<Message>> {
|
pub fn activity_list(&self, id: window::Id, opacity: f32) -> Element<'_, cosmic::Action<Message>> {
|
||||||
let palette = self.config.theme.palette();
|
let palette = self.config.theme.palette();
|
||||||
let name = self.output_for_bar(id).map(|output| output.name.as_str()).unwrap_or("");
|
let name = self.output_for_bar(id).map(|output| output.name.as_str()).unwrap_or("");
|
||||||
@@ -1021,9 +968,28 @@ impl FereseShell {
|
|||||||
});
|
});
|
||||||
let mut rows = Vec::with_capacity(entries.len());
|
let mut rows = Vec::with_capacity(entries.len());
|
||||||
if entries.is_empty() {
|
if entries.is_empty() {
|
||||||
rows.push(self.render.text("No active tasks").size(13).into());
|
rows.push(
|
||||||
|
container(
|
||||||
|
self.render
|
||||||
|
.text("No active tasks")
|
||||||
|
.size(12)
|
||||||
|
.class(theme::Text::Color(palette.muted.scale_alpha(opacity))),
|
||||||
|
)
|
||||||
|
.padding([8, 10])
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
for activity in entries {
|
for activity in entries {
|
||||||
|
// The timer card already represents this activity and owns its focus ID.
|
||||||
|
if self
|
||||||
|
.activities
|
||||||
|
.snapshot
|
||||||
|
.pomodoro
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|timer| timer.id == activity.id)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let mut text = column![
|
let mut text = column![
|
||||||
self.render
|
self.render
|
||||||
.text(activity.data.title.clone())
|
.text(activity.data.title.clone())
|
||||||
@@ -1129,12 +1095,12 @@ impl FereseShell {
|
|||||||
// Keep unnamed row roots: the toolkit's column does not move directly
|
// Keep unnamed row roots: the toolkit's column does not move directly
|
||||||
// named child trees when their order changes. Inner button IDs restore
|
// named child trees when their order changes. Inner button IDs restore
|
||||||
// focus through the normal named-state reconciliation on each rebuild.
|
// focus through the normal named-state reconciliation on each rebuild.
|
||||||
column![
|
let mut body = column![self.pomodoro_controls(id, opacity)]
|
||||||
self.pomodoro_controls(opacity),
|
.spacing(8)
|
||||||
column::with_children(rows).spacing(4).width(Length::Fill)
|
.width(Length::Fill);
|
||||||
]
|
if !rows.is_empty() {
|
||||||
.spacing(8)
|
body = body.push(column::with_children(rows).spacing(4).width(Length::Fill));
|
||||||
.width(Length::Fill)
|
}
|
||||||
.into()
|
body.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
//! Pomodoro uses the same type, material, and animated controls as status popups.
|
||||||
|
use crate::{FereseShell, Message, color_with_opacity, motion};
|
||||||
|
use cosmic::iced::{Alignment, Length, window};
|
||||||
|
use cosmic::widget::{button, column, row, tooltip};
|
||||||
|
use cosmic::{Element, theme};
|
||||||
|
use ferese_activities::pomodoro::{Phase, Status};
|
||||||
|
use ferese_ipc::activities::{PomodoroAction as Action, now_ms};
|
||||||
|
use ferese_theme::{icons, menus};
|
||||||
|
|
||||||
|
impl FereseShell {
|
||||||
|
pub(super) fn pomodoro_controls(&self, bar: window::Id, opacity: f32) -> Element<'_, cosmic::Action<Message>> {
|
||||||
|
let palette = self.config.theme;
|
||||||
|
let primary = color_with_opacity(palette.text_primary, opacity);
|
||||||
|
let muted = color_with_opacity(palette.text_muted, opacity);
|
||||||
|
let accent = color_with_opacity(palette.accent, opacity);
|
||||||
|
let current = self.activities.snapshot.pomodoro.as_ref();
|
||||||
|
let session = current.map(|current| current.id.clone());
|
||||||
|
let settings = current.map_or(&self.config.activities.pomodoro, |current| ¤t.timer.settings);
|
||||||
|
let (phase, seconds, description, label, action, symbol) = match current {
|
||||||
|
None => (
|
||||||
|
"Focus timer",
|
||||||
|
u64::from(settings.work_minutes) * 60,
|
||||||
|
format!(
|
||||||
|
"{} interval{} · {} min breaks",
|
||||||
|
settings.intervals,
|
||||||
|
if settings.intervals == 1 { "" } else { "s" },
|
||||||
|
settings.short_break_minutes
|
||||||
|
),
|
||||||
|
"Start Pomodoro",
|
||||||
|
Action::Start,
|
||||||
|
icons::MEDIA_PLAY,
|
||||||
|
),
|
||||||
|
Some(current) => {
|
||||||
|
let timer = ¤t.timer;
|
||||||
|
let phase = match timer.phase {
|
||||||
|
Phase::Work => "Focus",
|
||||||
|
Phase::ShortBreak => "Short break",
|
||||||
|
Phase::LongBreak => "Long break",
|
||||||
|
};
|
||||||
|
let description = if timer.status == Status::Finished {
|
||||||
|
format!(
|
||||||
|
"{} focus interval{} complete",
|
||||||
|
timer.completed_work,
|
||||||
|
if timer.completed_work == 1 { "" } else { "s" }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let interval = if timer.phase == Phase::Work {
|
||||||
|
format!(
|
||||||
|
"Interval {} of {}",
|
||||||
|
timer.completed_work.saturating_add(1).min(settings.intervals),
|
||||||
|
settings.intervals
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!("{} of {} complete", timer.completed_work, settings.intervals)
|
||||||
|
};
|
||||||
|
format!(
|
||||||
|
"{}{interval}",
|
||||||
|
match timer.status {
|
||||||
|
Status::Paused => "Paused · ",
|
||||||
|
Status::Ready => "Ready · ",
|
||||||
|
_ => "",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let (label, action, symbol) = match timer.status {
|
||||||
|
Status::Running => ("Pause", Action::Pause, icons::MEDIA_PAUSE),
|
||||||
|
Status::Paused => ("Resume", Action::Resume, icons::MEDIA_PLAY),
|
||||||
|
Status::Ready => (
|
||||||
|
if timer.phase == Phase::Work {
|
||||||
|
"Start focus"
|
||||||
|
} else {
|
||||||
|
"Start break"
|
||||||
|
},
|
||||||
|
Action::Resume,
|
||||||
|
icons::MEDIA_PLAY,
|
||||||
|
),
|
||||||
|
Status::Finished => ("Start again", Action::Start, icons::MEDIA_PLAY),
|
||||||
|
};
|
||||||
|
(
|
||||||
|
if timer.status == Status::Finished {
|
||||||
|
"Complete"
|
||||||
|
} else {
|
||||||
|
phase
|
||||||
|
},
|
||||||
|
timer.remaining(now_ms()).div_ceil(1_000),
|
||||||
|
description,
|
||||||
|
label,
|
||||||
|
action,
|
||||||
|
symbol,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let message = |action| cosmic::Action::App(Message::Pomodoro(action, session.clone()));
|
||||||
|
let badge = menus::badge(
|
||||||
|
icons::tinted(
|
||||||
|
if current.is_some_and(|current| current.timer.status == Status::Finished) {
|
||||||
|
icons::CHECK
|
||||||
|
} else {
|
||||||
|
icons::ACTIVITY_TIMER
|
||||||
|
},
|
||||||
|
20,
|
||||||
|
accent,
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
crate::color(palette.accent),
|
||||||
|
opacity,
|
||||||
|
self.render.radius(18.),
|
||||||
|
);
|
||||||
|
let heading = row![
|
||||||
|
badge,
|
||||||
|
column![
|
||||||
|
self.render.text("Pomodoro").size(13).class(theme::Text::Color(primary)),
|
||||||
|
self.render.text(phase).size(11).class(theme::Text::Color(muted)),
|
||||||
|
]
|
||||||
|
.spacing(3)
|
||||||
|
.width(Length::Fill),
|
||||||
|
self.render
|
||||||
|
.text(format!("{:02}:{:02}", seconds / 60, seconds % 60))
|
||||||
|
.size(26)
|
||||||
|
.width(96)
|
||||||
|
.align_x(cosmic::iced::alignment::Horizontal::Right)
|
||||||
|
.wrapping(cosmic::iced::widget::text::Wrapping::None)
|
||||||
|
.class(theme::Text::Color(primary)),
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(Alignment::Center);
|
||||||
|
let primary_button = button::custom(
|
||||||
|
row![icons::tinted(symbol, 16, accent), self.render.text(label).size(13)]
|
||||||
|
.spacing(6)
|
||||||
|
.align_y(Alignment::Center),
|
||||||
|
)
|
||||||
|
.id(session.as_ref().map_or_else(
|
||||||
|
|| cosmic::widget::Id::new(format!("pomodoro-start-{bar:?}")),
|
||||||
|
|id| super::activity_widget_id(bar, id),
|
||||||
|
))
|
||||||
|
.name(label)
|
||||||
|
.padding([7, 10])
|
||||||
|
.width(Length::Fill)
|
||||||
|
.on_press(message(action));
|
||||||
|
let mut controls = row![motion::button(self.render, primary_button, accent, true, opacity)]
|
||||||
|
.spacing(4)
|
||||||
|
.align_y(Alignment::Center);
|
||||||
|
let secondary = |label: &'static str, symbol, action| {
|
||||||
|
let control = button::custom(icons::tinted(symbol, 16, primary))
|
||||||
|
.name(label)
|
||||||
|
.padding(8)
|
||||||
|
.on_press(message(action));
|
||||||
|
tooltip(
|
||||||
|
motion::button(self.render, control, primary, false, opacity),
|
||||||
|
self.render.text(label).size(12).class(theme::Text::Color(primary)),
|
||||||
|
cosmic::widget::tooltip::Position::Top,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if let Some(current) = current {
|
||||||
|
if matches!(current.timer.status, Status::Running | Status::Paused) {
|
||||||
|
controls = controls.push(secondary("Skip", icons::MEDIA_NEXT, Action::Next));
|
||||||
|
}
|
||||||
|
if current.timer.status != Status::Finished {
|
||||||
|
controls = controls.push(secondary("Reset", icons::RESTART, Action::Reset));
|
||||||
|
}
|
||||||
|
controls = controls.push(secondary(
|
||||||
|
if current.timer.status == Status::Finished {
|
||||||
|
"Dismiss"
|
||||||
|
} else {
|
||||||
|
"Stop"
|
||||||
|
},
|
||||||
|
icons::RECORD_STOP,
|
||||||
|
Action::Stop,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
menus::section(
|
||||||
|
column![
|
||||||
|
heading,
|
||||||
|
self.render.text(description).size(11).class(theme::Text::Color(muted)),
|
||||||
|
controls,
|
||||||
|
]
|
||||||
|
.spacing(10)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.into(),
|
||||||
|
crate::color(palette.text_primary),
|
||||||
|
opacity,
|
||||||
|
self.render.radius(22.),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1512,12 +1512,18 @@ mod tests {
|
|||||||
self.0.push((text.into(), bounds));
|
self.0.push((text.into(), bounds));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let renderer = cosmic::iced::futures::executor::block_on(cosmic::Renderer::new(
|
let mut renderer = cosmic::iced::futures::executor::block_on(cosmic::Renderer::new(
|
||||||
cosmic::iced::Font::default(),
|
cosmic::iced::Font::default(),
|
||||||
cosmic::iced::Pixels(14.),
|
cosmic::iced::Pixels(14.),
|
||||||
Some("tiny-skia"),
|
Some("tiny-skia"),
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let mut app = shell();
|
||||||
|
app.config.font_family = Some("JetBrains Mono".into());
|
||||||
|
app.render = crate::rendering::Rendering::new(&app.config);
|
||||||
|
let bar = app.outputs[0].bar;
|
||||||
|
seed_activities(&mut app, 1);
|
||||||
|
let id = app.activities.snapshot.activities[0].id.clone();
|
||||||
for status in [
|
for status in [
|
||||||
None,
|
None,
|
||||||
Some(Status::Running),
|
Some(Status::Running),
|
||||||
@@ -1525,20 +1531,23 @@ mod tests {
|
|||||||
Some(Status::Ready),
|
Some(Status::Ready),
|
||||||
Some(Status::Finished),
|
Some(Status::Finished),
|
||||||
] {
|
] {
|
||||||
let mut app = shell();
|
|
||||||
let bar = app.outputs[0].bar;
|
|
||||||
seed_activities(&mut app, 1);
|
|
||||||
let id = app.activities.snapshot.activities[0].id.clone();
|
|
||||||
if let Some(status) = status {
|
if let Some(status) = status {
|
||||||
let mut timer = Session::start(Default::default(), ferese_ipc::activities::now_ms()).unwrap();
|
let mut timer = Session::start(Default::default(), ferese_ipc::activities::now_ms()).unwrap();
|
||||||
timer.status = status;
|
timer.status = status;
|
||||||
if status != Status::Running {
|
if status != Status::Running {
|
||||||
timer.deadline = None;
|
timer.deadline = None;
|
||||||
}
|
}
|
||||||
|
if status == Status::Finished {
|
||||||
|
timer.remaining_ms = 0;
|
||||||
|
timer.completed_work = timer.settings.intervals;
|
||||||
|
}
|
||||||
|
std::sync::Arc::make_mut(&mut app.activities.snapshot).activities[0]
|
||||||
|
.data
|
||||||
|
.title = "Pomodoro · Focus".into();
|
||||||
std::sync::Arc::make_mut(&mut app.activities.snapshot).pomodoro =
|
std::sync::Arc::make_mut(&mut app.activities.snapshot).pomodoro =
|
||||||
Some(ferese_ipc::activities::Pomodoro { id: id.clone(), timer });
|
Some(ferese_ipc::activities::Pomodoro { id: id.clone(), timer });
|
||||||
}
|
}
|
||||||
for width in [260., 312.] {
|
for width in [240., 260., 312.] {
|
||||||
let mut view = app.activity_list(bar, 1.);
|
let mut view = app.activity_list(bar, 1.);
|
||||||
let mut tree = widget::Tree::new(&view);
|
let mut tree = widget::Tree::new(&view);
|
||||||
let node = view.as_widget_mut().layout(
|
let node = view.as_widget_mut().layout(
|
||||||
@@ -1555,41 +1564,90 @@ mod tests {
|
|||||||
"{status:?} {width} {text}: {bounds:?}"
|
"{status:?} {width} {text}: {bounds:?}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let mut tab: Box<dyn widget::Operation> = Box::new(operation::focusable::focus_next());
|
assert!(
|
||||||
loop {
|
!labels.0.iter().any(|(text, _)| text == "Pomodoro · Focus"),
|
||||||
view.as_widget_mut()
|
"the timer must not appear twice"
|
||||||
.operate(&mut tree, Layout::new(&node), &renderer, &mut tab);
|
|
||||||
match tab.finish() {
|
|
||||||
operation::Outcome::Chain(next) => tab = next,
|
|
||||||
_ => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let event = Event::Keyboard(KeyEvent::KeyPressed {
|
|
||||||
key: Key::Named(key::Named::Enter),
|
|
||||||
modified_key: Key::Named(key::Named::Enter),
|
|
||||||
physical_key: key::Physical::Code(key::Code::Enter),
|
|
||||||
location: Location::Standard,
|
|
||||||
modifiers: Modifiers::empty(),
|
|
||||||
text: None,
|
|
||||||
repeat: false,
|
|
||||||
});
|
|
||||||
let mut messages = Vec::new();
|
|
||||||
view.as_widget_mut().update(
|
|
||||||
&mut tree,
|
|
||||||
&event,
|
|
||||||
Layout::new(&node),
|
|
||||||
mouse::Cursor::Unavailable,
|
|
||||||
&renderer,
|
|
||||||
&mut clipboard::Null,
|
|
||||||
&mut Shell::new(&mut messages),
|
|
||||||
&Rectangle::with_size(Size::new(width, 600.)),
|
|
||||||
);
|
);
|
||||||
let expected = match status {
|
if let Some(directory) = std::env::var_os("FERESE_POMODORO_CAPTURE_DIR") {
|
||||||
|
use cosmic::iced::advanced::renderer::Renderer as _;
|
||||||
|
let directory = std::path::PathBuf::from(directory);
|
||||||
|
std::fs::create_dir_all(&directory).unwrap();
|
||||||
|
let viewport = Rectangle::with_size(node.size());
|
||||||
|
renderer.reset(viewport);
|
||||||
|
view.as_widget().draw(
|
||||||
|
&tree,
|
||||||
|
&mut renderer,
|
||||||
|
&app.config.theme.palette().native_theme(),
|
||||||
|
&cosmic::iced::advanced::renderer::Style {
|
||||||
|
text_color: crate::color(app.config.theme.text_primary),
|
||||||
|
icon_color: crate::color(app.config.theme.text_primary),
|
||||||
|
scale_factor: 1.5,
|
||||||
|
},
|
||||||
|
Layout::new(&node),
|
||||||
|
mouse::Cursor::Unavailable,
|
||||||
|
&viewport,
|
||||||
|
);
|
||||||
|
let physical = Size::new(
|
||||||
|
(node.size().width * 1.5).ceil() as u32,
|
||||||
|
(node.size().height * 1.5).ceil() as u32,
|
||||||
|
);
|
||||||
|
let pixels = renderer.screenshot(physical, 1.5, crate::color(app.config.theme.surface_popover));
|
||||||
|
image_codec::save_buffer(
|
||||||
|
directory.join(format!("{status:?}-{width}.png")),
|
||||||
|
&pixels,
|
||||||
|
physical.width,
|
||||||
|
physical.height,
|
||||||
|
image_codec::ColorType::Rgba8,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
let primary = match status {
|
||||||
Some(Status::Running) => PomodoroAction::Pause,
|
Some(Status::Running) => PomodoroAction::Pause,
|
||||||
Some(Status::Paused | Status::Ready) => PomodoroAction::Resume,
|
Some(Status::Paused | Status::Ready) => PomodoroAction::Resume,
|
||||||
_ => PomodoroAction::Start,
|
_ => PomodoroAction::Start,
|
||||||
};
|
};
|
||||||
assert!(messages.iter().any(|message|matches!(message,cosmic::Action::App(Message::Pomodoro(action,session)) if *action==expected && *session==status.map(|_|id.clone()))),"{status:?}: {messages:?}");
|
let mut expected = vec![primary];
|
||||||
|
if matches!(status, Some(Status::Running | Status::Paused)) {
|
||||||
|
expected.push(PomodoroAction::Next);
|
||||||
|
}
|
||||||
|
if status.is_some_and(|status| status != Status::Finished) {
|
||||||
|
expected.push(PomodoroAction::Reset);
|
||||||
|
}
|
||||||
|
if status.is_some() {
|
||||||
|
expected.push(PomodoroAction::Stop);
|
||||||
|
}
|
||||||
|
for expected in expected {
|
||||||
|
let mut tab: Box<dyn widget::Operation> = Box::new(operation::focusable::focus_next());
|
||||||
|
loop {
|
||||||
|
view.as_widget_mut()
|
||||||
|
.operate(&mut tree, Layout::new(&node), &renderer, &mut tab);
|
||||||
|
match tab.finish() {
|
||||||
|
operation::Outcome::Chain(next) => tab = next,
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let event = Event::Keyboard(KeyEvent::KeyPressed {
|
||||||
|
key: Key::Named(key::Named::Enter),
|
||||||
|
modified_key: Key::Named(key::Named::Enter),
|
||||||
|
physical_key: key::Physical::Code(key::Code::Enter),
|
||||||
|
location: Location::Standard,
|
||||||
|
modifiers: Modifiers::empty(),
|
||||||
|
text: None,
|
||||||
|
repeat: false,
|
||||||
|
});
|
||||||
|
let mut messages = Vec::new();
|
||||||
|
view.as_widget_mut().update(
|
||||||
|
&mut tree,
|
||||||
|
&event,
|
||||||
|
Layout::new(&node),
|
||||||
|
mouse::Cursor::Unavailable,
|
||||||
|
&renderer,
|
||||||
|
&mut clipboard::Null,
|
||||||
|
&mut Shell::new(&mut messages),
|
||||||
|
&Rectangle::with_size(Size::new(width, 600.)),
|
||||||
|
);
|
||||||
|
assert!(messages.iter().any(|message|matches!(message,cosmic::Action::App(Message::Pomodoro(action,session)) if *action==expected && *session==status.map(|_|id.clone()))),"{status:?} {expected:?}: {messages:?}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user