Improve display of paused state

This commit is contained in:
Jeremy Soller 2024-11-15 11:07:26 -07:00
parent 2109c8c3d6
commit 49595d87f3
No known key found for this signature in database
GPG key ID: D02FD439211AF56F
3 changed files with 90 additions and 58 deletions

View file

@ -29,7 +29,8 @@ size = Size
# Progress footer # Progress footer
details = Details details = Details
dismiss = Dismiss message dismiss = Dismiss message
operations-in-progress = {$count} actions in progress ({$percent}%)... operations-running = {$running} operations running ({$percent}%)...
operations-running-finished = {$running} operations running ({$percent}%), {$finished} finished...
pause = Pause pause = Pause
resume = Resume resume = Resume
@ -138,12 +139,15 @@ edit-history = Edit history
history = History history = History
no-history = No items in history. no-history = No items in history.
pending = Pending pending = Pending
progress = {$percent}%
progress-cancelled = {$percent}%, cancelled
progress-paused = {$percent}%, paused
failed = Failed failed = Failed
complete = Complete complete = Complete
compressing = Compressing {$items} {$items -> compressing = Compressing {$items} {$items ->
[one] item [one] item
*[other] items *[other] items
} from "{$from}" to "{$to}" ({$percent}%)... } from "{$from}" to "{$to}" ({$progress})...
compressed = Compressed {$items} {$items -> compressed = Compressed {$items} {$items ->
[one] item [one] item
*[other] items *[other] items
@ -154,17 +158,17 @@ created = Created "{$name}" in "{$parent}"
copying = Copying {$items} {$items -> copying = Copying {$items} {$items ->
[one] item [one] item
*[other] items *[other] items
} from "{$from}" to "{$to}" ({$percent}%)... } from "{$from}" to "{$to}" ({$progress})...
copied = Copied {$items} {$items -> copied = Copied {$items} {$items ->
[one] item [one] item
*[other] items *[other] items
} from "{$from}" to "{$to}" } from "{$from}" to "{$to}"
emptying-trash = Emptying {trash} ({$percent}%)... emptying-trash = Emptying {trash} ({$progress})...
emptied-trash = Emptied {trash} emptied-trash = Emptied {trash}
extracting = Extracting {$items} {$items -> extracting = Extracting {$items} {$items ->
[one] item [one] item
*[other] items *[other] items
} from "{$from}" to "{$to}" ({$percent}%)... } from "{$from}" to "{$to}" ({$progress})...
extracted = Extracted {$items} {$items -> extracted = Extracted {$items} {$items ->
[one] item [one] item
*[other] items *[other] items
@ -174,7 +178,7 @@ set-executable-and-launched = Set "{$name}" as executable and launched
moving = Moving {$items} {$items -> moving = Moving {$items} {$items ->
[one] item [one] item
*[other] items *[other] items
} from "{$from}" to "{$to}" ({$percent}%)... } from "{$from}" to "{$to}" ({$progress})...
moved = Moved {$items} {$items -> moved = Moved {$items} {$items ->
[one] item [one] item
*[other] items *[other] items
@ -184,7 +188,7 @@ renamed = Renamed "{$from}" to "{$to}"
restoring = Restoring {$items} {$items -> restoring = Restoring {$items} {$items ->
[one] item [one] item
*[other] items *[other] items
} from {trash} ({$percent})... } from {trash} ({$progress})...
restored = Restored {$items} {$items -> restored = Restored {$items} {$items ->
[one] item [one] item
*[other] items *[other] items

View file

@ -524,7 +524,7 @@ pub struct App {
pending_operations: BTreeMap<u64, (Operation, f32, Controller)>, pending_operations: BTreeMap<u64, (Operation, f32, Controller)>,
progress_operations: BTreeSet<u64>, progress_operations: BTreeSet<u64>,
complete_operations: BTreeMap<u64, Operation>, complete_operations: BTreeMap<u64, Operation>,
failed_operations: BTreeMap<u64, (Operation, f32, String)>, failed_operations: BTreeMap<u64, (Operation, f32, Controller, String)>,
search_id: widget::Id, search_id: widget::Id,
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
surface_ids: HashMap<WlOutput, WindowId>, surface_ids: HashMap<WlOutput, WindowId>,
@ -1218,6 +1218,8 @@ impl App {
} }
fn edit_history(&self) -> Element<Message> { fn edit_history(&self) -> Element<Message> {
let cosmic_theme::Spacing { space_m, .. } = theme::active().cosmic().spacing;
let mut children = Vec::new(); let mut children = Vec::new();
//TODO: get height from theme? //TODO: get height from theme?
@ -1265,7 +1267,7 @@ impl App {
]) ])
.align_y(Alignment::Center) .align_y(Alignment::Center)
.into(), .into(),
widget::text(op.pending_text(*progress as i32)).into(), widget::text(op.pending_text(*progress as i32, controller.state())).into(),
])); ]));
} }
children.push(section.into()); children.push(section.into());
@ -1273,9 +1275,9 @@ impl App {
if !self.failed_operations.is_empty() { if !self.failed_operations.is_empty() {
let mut section = widget::settings::section().title(fl!("failed")); let mut section = widget::settings::section().title(fl!("failed"));
for (_id, (op, progress, error)) in self.failed_operations.iter().rev() { for (_id, (op, progress, controller, error)) in self.failed_operations.iter().rev() {
section = section.add(widget::column::with_children(vec![ section = section.add(widget::column::with_children(vec![
widget::text(op.pending_text(*progress as i32)).into(), widget::text(op.pending_text(*progress as i32, controller.state())).into(),
widget::text(error).into(), widget::text(error).into(),
])); ]));
} }
@ -1294,7 +1296,9 @@ impl App {
children.push(widget::text::body(fl!("no-history")).into()); children.push(widget::text::body(fl!("no-history")).into());
} }
widget::column::with_children(children).into() widget::column::with_children(children)
.spacing(space_m)
.into()
} }
fn preview<'a>( fn preview<'a>(
@ -2419,12 +2423,14 @@ impl Application for App {
} }
Message::PendingError(id, err) => { Message::PendingError(id, err) => {
if let Some((op, progress, controller)) = self.pending_operations.remove(&id) { if let Some((op, progress, controller)) = self.pending_operations.remove(&id) {
self.failed_operations.insert(id, (op, progress, err));
// Only show dialog if not cancelled // Only show dialog if not cancelled
if !controller.is_cancelled() { if !controller.is_cancelled() {
self.dialog_pages.push_back(DialogPage::FailedOperation(id)); self.dialog_pages.push_back(DialogPage::FailedOperation(id));
} }
// Remove from progress
self.progress_operations.remove(&id); self.progress_operations.remove(&id);
self.failed_operations
.insert(id, (op, progress, controller, err));
} }
// Close progress notification if all relavent operations are finished // Close progress notification if all relavent operations are finished
if !self if !self
@ -3421,7 +3427,7 @@ impl Application for App {
), ),
DialogPage::FailedOperation(id) => { DialogPage::FailedOperation(id) => {
//TODO: try next dialog page (making sure index is used by Dialog messages)? //TODO: try next dialog page (making sure index is used by Dialog messages)?
let (operation, _, err) = self.failed_operations.get(id)?; let (operation, _, _, err) = self.failed_operations.get(id)?;
//TODO: nice description of error //TODO: nice description of error
widget::dialog() widget::dialog()
@ -3874,25 +3880,37 @@ impl Application for App {
} }
if op.show_progress_notification() { if op.show_progress_notification() {
if title.is_empty() { if title.is_empty() {
title = op.pending_text(*progress as i32); title = op.pending_text(*progress as i32, controller.state());
} }
total_progress += progress; total_progress += progress;
count += 1; count += 1;
} }
} }
let in_progress_count = count; let running = count;
// Adjust the progress bar so it does not jump around when operations finish // Adjust the progress bar so it does not jump around when operations finish
while count < self.progress_operations.len() { for id in self.progress_operations.iter() {
total_progress += 100.0; if self.complete_operations.contains_key(&id) {
count += 1; total_progress += 100.0;
count += 1;
}
} }
let finished = count - running;
total_progress /= count as f32; total_progress /= count as f32;
if in_progress_count > 1 { if running > 1 {
title = fl!( if finished > 0 {
"operations-in-progress", title = fl!(
count = in_progress_count, "operations-running-finished",
percent = (total_progress as i32) running = running,
); finished = finished,
percent = (total_progress as i32)
);
} else {
title = fl!(
"operations-running",
running = running,
percent = (total_progress as i32)
);
}
} }
//TODO: get height from theme? //TODO: get height from theme?

View file

@ -224,8 +224,15 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
Ok(()) Ok(())
} }
#[derive(Clone, Copy, Debug)]
pub enum ControllerState {
Cancelled,
Paused,
Running,
}
struct ControllerInner { struct ControllerInner {
state: Mutex<u32>, state: Mutex<ControllerState>,
condvar: Condvar, condvar: Condvar,
} }
@ -235,14 +242,10 @@ pub struct Controller {
} }
impl Controller { impl Controller {
const RUNNING: u32 = 0;
const PAUSED: u32 = 1;
const CANCELLED: u32 = 2;
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
inner: Arc::new(ControllerInner { inner: Arc::new(ControllerInner {
state: Mutex::new(Self::RUNNING), state: Mutex::new(ControllerState::Running),
condvar: Condvar::new(), condvar: Condvar::new(),
}), }),
} }
@ -251,42 +254,44 @@ impl Controller {
pub fn check(&self) -> Result<(), String> { pub fn check(&self) -> Result<(), String> {
let mut state = self.inner.state.lock().unwrap(); let mut state = self.inner.state.lock().unwrap();
loop { loop {
if *state == Self::CANCELLED { match *state {
return Err(fl!("cancelled")); ControllerState::Cancelled => return Err(fl!("cancelled")),
} else if *state == Self::PAUSED { ControllerState::Paused => {
state = self.inner.condvar.wait(state).unwrap(); state = self.inner.condvar.wait(state).unwrap();
} else { }
return Ok(()); ControllerState::Running => return Ok(()),
} }
} }
} }
pub fn state(&self) -> ControllerState {
*self.inner.state.lock().unwrap()
}
pub fn set_state(&self, state: ControllerState) {
*self.inner.state.lock().unwrap() = state;
self.inner.condvar.notify_all();
}
pub fn is_cancelled(&self) -> bool { pub fn is_cancelled(&self) -> bool {
let state = self.inner.state.lock().unwrap(); matches!(self.state(), ControllerState::Cancelled)
*state == Self::CANCELLED
} }
pub fn cancel(&self) { pub fn cancel(&self) {
let mut state = self.inner.state.lock().unwrap(); self.set_state(ControllerState::Cancelled);
*state = Self::CANCELLED;
self.inner.condvar.notify_all();
} }
pub fn is_paused(&self) -> bool { pub fn is_paused(&self) -> bool {
let state = self.inner.state.lock().unwrap(); matches!(self.state(), ControllerState::Paused)
*state == Self::PAUSED
} }
pub fn pause(&self) { pub fn pause(&self) {
let mut state = self.inner.state.lock().unwrap(); self.set_state(ControllerState::Paused);
*state = Self::PAUSED;
self.inner.condvar.notify_all();
} }
pub fn unpause(&self) { pub fn unpause(&self) {
let mut state = self.inner.state.lock().unwrap(); //TODO: ensure this does not override Cancel?
*state = Self::RUNNING; self.set_state(ControllerState::Running);
self.inner.condvar.notify_all();
} }
} }
@ -528,43 +533,48 @@ fn paths_parent_name<'a>(paths: &'a Vec<PathBuf>) -> Cow<'a, str> {
} }
impl Operation { impl Operation {
pub fn pending_text(&self, percent: i32) -> String { pub fn pending_text(&self, percent: i32, state: ControllerState) -> String {
let progress = || match state {
ControllerState::Running => fl!("progress", percent = percent),
ControllerState::Paused => fl!("progress-paused", percent = percent),
ControllerState::Cancelled => fl!("progress-cancelled", percent = percent),
};
match self { match self {
Self::Compress { paths, to, .. } => fl!( Self::Compress { paths, to, .. } => fl!(
"compressing", "compressing",
items = paths.len(), items = paths.len(),
from = paths_parent_name(paths), from = paths_parent_name(paths),
to = file_name(to), to = file_name(to),
percent = percent progress = progress()
), ),
Self::Copy { paths, to } => fl!( Self::Copy { paths, to } => fl!(
"copying", "copying",
items = paths.len(), items = paths.len(),
from = paths_parent_name(paths), from = paths_parent_name(paths),
to = file_name(to), to = file_name(to),
percent = percent progress = progress()
), ),
Self::Delete { paths } => fl!( Self::Delete { paths } => fl!(
"moving", "moving",
items = paths.len(), items = paths.len(),
from = paths_parent_name(paths), from = paths_parent_name(paths),
to = fl!("trash"), to = fl!("trash"),
percent = percent progress = progress()
), ),
Self::EmptyTrash => fl!("emptying-trash", percent = percent), Self::EmptyTrash => fl!("emptying-trash", progress = progress()),
Self::Extract { paths, to } => fl!( Self::Extract { paths, to } => fl!(
"extracting", "extracting",
items = paths.len(), items = paths.len(),
from = paths_parent_name(paths), from = paths_parent_name(paths),
to = file_name(to), to = file_name(to),
percent = percent progress = progress()
), ),
Self::Move { paths, to } => fl!( Self::Move { paths, to } => fl!(
"moving", "moving",
items = paths.len(), items = paths.len(),
from = paths_parent_name(paths), from = paths_parent_name(paths),
to = file_name(to), to = file_name(to),
percent = percent progress = progress()
), ),
Self::NewFile { path } => fl!( Self::NewFile { path } => fl!(
"creating", "creating",
@ -579,7 +589,7 @@ impl Operation {
Self::Rename { from, to } => { Self::Rename { from, to } => {
fl!("renaming", from = file_name(from), to = file_name(to)) fl!("renaming", from = file_name(from), to = file_name(to))
} }
Self::Restore { paths } => fl!("restoring", items = paths.len(), percent = percent), Self::Restore { paths } => fl!("restoring", items = paths.len(), progress = progress()),
Self::SetExecutableAndLaunch { path } => { Self::SetExecutableAndLaunch { path } => {
fl!("setting-executable-and-launching", name = file_name(path)) fl!("setting-executable-and-launching", name = file_name(path))
} }