Add dialog types

This commit is contained in:
Jeremy Soller 2024-02-15 15:03:01 -07:00
parent 91c4985d42
commit a24983ca7f
No known key found for this signature in database
GPG key ID: D02FD439211AF56F
4 changed files with 244 additions and 211 deletions

View file

@ -4,9 +4,11 @@ use cosmic::{
iced::{subscription::Subscription, window}, iced::{subscription::Subscription, window},
widget, Application, Element, widget, Application, Element,
}; };
use cosmic_files::dialog::{Dialog, DialogMessage, DialogResult}; use cosmic_files::dialog::{Dialog, DialogKind, DialogMessage, DialogResult};
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
let settings = Settings::default(); let settings = Settings::default();
app::run::<App>(settings, ())?; app::run::<App>(settings, ())?;
Ok(()) Ok(())
@ -17,6 +19,7 @@ pub enum Message {
DialogMessage(DialogMessage), DialogMessage(DialogMessage),
DialogOpen, DialogOpen,
DialogResult(DialogResult), DialogResult(DialogResult),
DialogSave,
} }
pub struct App { pub struct App {
@ -60,8 +63,12 @@ impl Application for App {
} }
Message::DialogOpen => { Message::DialogOpen => {
if self.dialog_opt.is_none() { if self.dialog_opt.is_none() {
let (dialog, command) = let (dialog, command) = Dialog::new(
Dialog::new(Message::DialogMessage, Message::DialogResult); DialogKind::OpenFile,
None,
Message::DialogMessage,
Message::DialogResult,
);
self.dialog_opt = Some(dialog); self.dialog_opt = Some(dialog);
return command; return command;
} }
@ -70,6 +77,18 @@ impl Application for App {
self.dialog_opt = None; self.dialog_opt = None;
self.result_opt = Some(result); self.result_opt = Some(result);
} }
Message::DialogSave => {
if self.dialog_opt.is_none() {
let (dialog, command) = Dialog::new(
DialogKind::SaveFile,
Some("README.md".into()),
Message::DialogMessage,
Message::DialogResult,
);
self.dialog_opt = Some(dialog);
return command;
}
}
} }
Command::none() Command::none()
@ -83,12 +102,21 @@ impl Application for App {
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<Message> {
let mut button = widget::button(widget::text("Open Dialog")); let mut column = widget::column().spacing(8);
if self.dialog_opt.is_none() { {
button = button.on_press(Message::DialogOpen); let mut button = widget::button(widget::text("Open Dialog"));
if self.dialog_opt.is_none() {
button = button.on_press(Message::DialogOpen);
}
column = column.push(button);
}
{
let mut button = widget::button(widget::text("Save Dialog"));
if self.dialog_opt.is_none() {
button = button.on_press(Message::DialogSave);
}
column = column.push(button);
} }
let mut column = widget::column();
column = column.push(button);
if let Some(result) = &self.result_opt { if let Some(result) = &self.result_opt {
match result { match result {
DialogResult::Cancel => { DialogResult::Cancel => {

View file

@ -7,6 +7,12 @@ trash = Trash
# Dialog # Dialog
cancel = Cancel cancel = Cancel
open = Open open = Open
open-file = Open file
open-folder = Open folder
open-multiple-files = Open multiple files
open-multiple-folders = Open multiple folders
save = Save
save-file = Save file
# List view # List view
name = Name name = Name

View file

@ -2,11 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use cosmic::{ use cosmic::{
app::{ app::{self, cosmic::Cosmic, message, Command, Core},
self,
cosmic::{Cosmic, Message as CosmicMessage},
message, Command, Core,
},
cosmic_theme, executor, cosmic_theme, executor,
iced::{ iced::{
event, event,
@ -16,18 +12,11 @@ use cosmic::{
subscription::{self, Subscription}, subscription::{self, Subscription},
window, Event, Length, Size, window, Event, Length, Size,
}, },
style,
widget::{self, segmented_button}, widget::{self, segmented_button},
Application, ApplicationExt, Element, Application, ApplicationExt, Element,
}; };
use notify::Watcher; use notify::Watcher;
use std::{ use std::{any::TypeId, collections::HashSet, env, fs, path::PathBuf, time};
any::TypeId,
collections::HashSet,
path::PathBuf,
sync::{Arc, Mutex},
time,
};
use crate::{ use crate::{
config::TabConfig, config::TabConfig,
@ -44,17 +33,51 @@ pub enum DialogResult {
Open(Vec<PathBuf>), Open(Vec<PathBuf>),
} }
#[derive(Copy, Clone, Debug)]
pub enum DialogKind {
OpenFile,
OpenFolder,
OpenMultipleFiles,
OpenMultipleFolders,
SaveFile,
}
impl DialogKind {
pub fn title(&self) -> String {
match self {
Self::OpenFile => fl!("open-file"),
Self::OpenFolder => fl!("open-folder"),
Self::OpenMultipleFiles => fl!("open-multiple-files"),
Self::OpenMultipleFolders => fl!("open-multiple-folders"),
Self::SaveFile => fl!("save-file"),
}
}
pub fn multiple(&self) -> bool {
matches!(self, Self::OpenMultipleFiles | Self::OpenMultipleFolders)
}
pub fn save(&self) -> bool {
matches!(self, Self::SaveFile)
}
}
pub struct Dialog<M> { pub struct Dialog<M> {
cosmic: Cosmic<App>, cosmic: Cosmic<App>,
mapper: fn(DialogMessage) -> M, mapper: fn(DialogMessage) -> M,
on_result: fn(DialogResult) -> M, on_result: Box<dyn Fn(DialogResult) -> M>,
} }
impl<M: 'static> Dialog<M> { impl<M: Send + 'static> Dialog<M> {
pub fn new( pub fn new(
kind: DialogKind,
path_opt: Option<PathBuf>,
mapper: fn(DialogMessage) -> M, mapper: fn(DialogMessage) -> M,
on_result: fn(DialogResult) -> M, on_result: impl Fn(DialogResult) -> M + 'static,
) -> (Self, Command<M>) { ) -> (Self, Command<M>) {
//TODO: only do this once somehow?
crate::localize::localize();
let mut settings = window::Settings::default(); let mut settings = window::Settings::default();
settings.decorations = false; settings.decorations = false;
settings.exit_on_close_request = false; settings.exit_on_close_request = false;
@ -72,14 +95,26 @@ impl<M: 'static> Dialog<M> {
let (window_id, window_command) = window::spawn(settings); let (window_id, window_command) = window::spawn(settings);
let core = Core::default(); let core = Core::default();
let flags = Flags { window_id }; let flags = Flags {
kind,
path_opt: path_opt
.as_ref()
.and_then(|path| match fs::canonicalize(path) {
Ok(ok) => Some(ok),
Err(err) => {
log::warn!("failed to canonicalize {:?}: {}", path, err);
None
}
}),
window_id,
};
let (cosmic, cosmic_command) = <Cosmic<App> as IcedApplication>::new((core, flags)); let (cosmic, cosmic_command) = <Cosmic<App> as IcedApplication>::new((core, flags));
( (
Self { Self {
cosmic, cosmic,
mapper, mapper,
on_result, on_result: Box::new(on_result),
}, },
Command::batch([window_command, cosmic_command]) Command::batch([window_command, cosmic_command])
.map(DialogMessage) .map(DialogMessage)
@ -102,10 +137,10 @@ impl<M: 'static> Dialog<M> {
.map(DialogMessage) .map(DialogMessage)
.map(move |message| app::Message::App(mapper(message))); .map(move |message| app::Message::App(mapper(message)));
if let Some(result) = self.cosmic.app.result_opt.take() { if let Some(result) = self.cosmic.app.result_opt.take() {
let on_result = self.on_result; let on_result_message = (self.on_result)(result);
Command::batch([ Command::batch([
command, command,
Command::perform(async move { app::Message::App(on_result(result)) }, |x| x), Command::perform(async move { app::Message::App(on_result_message) }, |x| x),
]) ])
} else { } else {
command command
@ -122,6 +157,8 @@ impl<M: 'static> Dialog<M> {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct Flags { struct Flags {
kind: DialogKind,
path_opt: Option<PathBuf>,
window_id: window::Id, window_id: window::Id,
} }
@ -129,15 +166,14 @@ struct Flags {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
enum Message { enum Message {
Cancel, Cancel,
Filename(String),
Modifiers(Modifiers), Modifiers(Modifiers),
NotifyEvent(notify::Event), NotifyEvent(notify::Event),
NotifyWatcher(WatcherWrapper), NotifyWatcher(WatcherWrapper),
Open, Open,
SelectAll(Option<segmented_button::Entity>), Save,
TabActivate(segmented_button::Entity), TabMessage(tab::Message),
TabClose(Option<segmented_button::Entity>), TabRescan(Vec<tab::Item>),
TabMessage(Option<segmented_button::Entity>, tab::Message),
TabRescan(segmented_button::Entity, Vec<tab::Item>),
} }
#[derive(Debug)] #[derive(Debug)]
@ -161,41 +197,22 @@ impl PartialEq for WatcherWrapper {
struct App { struct App {
core: Core, core: Core,
flags: Flags, flags: Flags,
filename: String,
filename_id: widget::Id,
modifiers: Modifiers, modifiers: Modifiers,
nav_model: segmented_button::SingleSelectModel, nav_model: segmented_button::SingleSelectModel,
result_opt: Option<DialogResult>, result_opt: Option<DialogResult>,
tab_model: segmented_button::Model<segmented_button::SingleSelect>, tab: Tab,
watcher_opt: Option<(notify::RecommendedWatcher, HashSet<PathBuf>)>, watcher_opt: Option<(notify::RecommendedWatcher, HashSet<PathBuf>)>,
} }
impl App { impl App {
fn open_tab(&mut self, location: Location) -> Command<Message> { fn rescan_tab(&self) -> Command<Message> {
let mut tab = Tab::new(location.clone(), TabConfig::default()); let location = self.tab.location.clone();
tab.dialog = true;
let entity = self
.tab_model
.insert()
.text(tab.title())
.data(tab)
.closable()
.activate()
.id();
Command::batch([
self.update_title(),
self.update_watcher(),
self.rescan_tab(entity, location),
])
}
fn rescan_tab(
&mut self,
entity: segmented_button::Entity,
location: Location,
) -> Command<Message> {
Command::perform( Command::perform(
async move { async move {
match tokio::task::spawn_blocking(move || location.scan()).await { match tokio::task::spawn_blocking(move || location.scan()).await {
Ok(items) => message::app(Message::TabRescan(entity, items)), Ok(items) => message::app(Message::TabRescan(items)),
Err(err) => { Err(err) => {
log::warn!("failed to rescan: {}", err); log::warn!("failed to rescan: {}", err);
message::none() message::none()
@ -207,26 +224,16 @@ impl App {
} }
fn update_title(&mut self) -> Command<Message> { fn update_title(&mut self) -> Command<Message> {
let (header_title, window_title) = match self.tab_model.text(self.tab_model.active()) { let title = self.flags.kind.title();
Some(tab_title) => ( self.set_header_title(title.clone());
tab_title.to_string(), self.set_window_title(title, self.main_window_id())
format!("{tab_title} — COSMIC File Manager"),
),
None => (String::new(), "COSMIC File Manager".to_string()),
};
self.set_header_title(header_title);
self.set_window_title(window_title, self.main_window_id())
} }
fn update_watcher(&mut self) -> Command<Message> { fn update_watcher(&mut self) -> Command<Message> {
if let Some((mut watcher, old_paths)) = self.watcher_opt.take() { if let Some((mut watcher, old_paths)) = self.watcher_opt.take() {
let mut new_paths = HashSet::new(); let mut new_paths = HashSet::new();
for entity in self.tab_model.iter() { if let Location::Path(path) = &self.tab.location {
if let Some(tab) = self.tab_model.data::<Tab>(entity) { new_paths.insert(path.clone());
if let Location::Path(path) = &tab.location {
new_paths.insert(path.clone());
}
}
} }
// Unwatch paths no longer used // Unwatch paths no longer used
@ -320,23 +327,44 @@ impl Application for App {
} }
} }
let mut filename = String::new();
let location = Location::Path(match &flags.path_opt {
Some(path) => {
if path.is_dir() {
path.to_path_buf()
} else if let Some(parent) = path.parent() {
if let Some(filename_os) = path.file_name() {
filename = filename_os.to_str().unwrap_or_default().to_string();
}
parent.to_path_buf()
} else {
path.to_path_buf()
}
}
None => match env::current_dir() {
Ok(path) => path,
Err(_) => home_dir(),
},
});
let mut tab = Tab::new(location, TabConfig::default());
tab.dialog = Some(flags.kind);
let mut app = App { let mut app = App {
core, core,
flags, flags,
filename,
filename_id: widget::Id::unique(),
modifiers: Modifiers::empty(), modifiers: Modifiers::empty(),
nav_model: nav_model.build(), nav_model: nav_model.build(),
result_opt: None, result_opt: None,
tab_model: segmented_button::ModelBuilder::default().build(), tab,
watcher_opt: None, watcher_opt: None,
}; };
let mut commands = Vec::new(); let commands = Command::batch([app.update_title(), app.update_watcher(), app.rescan_tab()]);
if app.tab_model.iter().next().is_none() { (app, commands)
commands.push(app.open_tab(Location::Path(home_dir())));
}
(app, Command::batch(commands))
} }
fn main_window_id(&self) -> window::Id { fn main_window_id(&self) -> window::Id {
@ -374,7 +402,7 @@ impl Application for App {
let location_opt = self.nav_model.data::<Location>(entity).clone(); let location_opt = self.nav_model.data::<Location>(entity).clone();
if let Some(location) = location_opt { if let Some(location) = location_opt {
let message = Message::TabMessage(None, tab::Message::Location(location.clone())); let message = Message::TabMessage(tab::Message::Location(location.clone()));
return self.update(message); return self.update(message);
} }
@ -388,40 +416,38 @@ impl Application for App {
self.result_opt = Some(DialogResult::Cancel); self.result_opt = Some(DialogResult::Cancel);
return window::close(self.main_window_id()); return window::close(self.main_window_id());
} }
Message::Filename(filename) => {
self.filename = filename;
// Select based on filename
if let Some(items) = &mut self.tab.items_opt {
for item in items.iter_mut() {
item.selected = item.name == self.filename;
}
}
}
Message::Modifiers(modifiers) => { Message::Modifiers(modifiers) => {
self.modifiers = modifiers; self.modifiers = modifiers;
} }
Message::NotifyEvent(event) => { Message::NotifyEvent(event) => {
log::debug!("{:?}", event); log::debug!("{:?}", event);
let mut needs_reload = Vec::new(); if let Location::Path(path) = &self.tab.location {
for entity in self.tab_model.iter() { let mut contains_change = false;
if let Some(tab) = self.tab_model.data::<Tab>(entity) { for event_path in event.paths.iter() {
//TODO: support reloading trash, somehow if event_path.starts_with(&path) {
if let Location::Path(path) = &tab.location { contains_change = true;
let mut contains_change = false; break;
for event_path in event.paths.iter() {
if event_path.starts_with(&path) {
contains_change = true;
break;
}
}
if contains_change {
needs_reload.push((entity, tab.location.clone()));
}
} }
} }
if contains_change {
return self.rescan_tab();
}
} }
let mut commands = Vec::with_capacity(needs_reload.len());
for (entity, location) in needs_reload {
commands.push(self.rescan_tab(entity, location));
}
return Command::batch(commands);
} }
Message::NotifyWatcher(mut watcher_wrapper) => match watcher_wrapper.watcher_opt.take() Message::NotifyWatcher(mut watcher_wrapper) => match watcher_wrapper.watcher_opt.take()
{ {
Some(mut watcher) => { Some(watcher) => {
self.watcher_opt = Some((watcher, HashSet::new())); self.watcher_opt = Some((watcher, HashSet::new()));
return self.update_watcher(); return self.update_watcher();
} }
@ -431,86 +457,67 @@ impl Application for App {
}, },
Message::Open => { Message::Open => {
let mut paths = Vec::new(); let mut paths = Vec::new();
let entity = self.tab_model.active(); if let Some(ref mut items) = self.tab.items_opt {
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) { for item in items.iter_mut() {
if let Some(ref mut items) = tab.items_opt { if item.selected {
for item in items.iter_mut() { paths.push(item.path.clone());
if item.selected {
paths.push(item.path.clone());
}
} }
} }
} }
self.result_opt = Some(DialogResult::Open(paths)); if !paths.is_empty() {
return window::close(self.main_window_id()); self.result_opt = Some(DialogResult::Open(paths));
}
Message::SelectAll(entity_opt) => {
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
if let Some(ref mut items) = tab.items_opt {
for item in items.iter_mut() {
if !tab.config.show_hidden && item.hidden {
continue;
}
item.selected = true;
item.click_time = None;
}
}
}
}
Message::TabActivate(entity) => {
self.tab_model.activate(entity);
return self.update_title();
}
Message::TabClose(entity_opt) => {
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
// Activate closest item
if let Some(position) = self.tab_model.position(entity) {
if position > 0 {
self.tab_model.activate_position(position - 1);
} else {
self.tab_model.activate_position(position + 1);
}
}
// Remove item
self.tab_model.remove(entity);
// If that was the last tab, close window
if self.tab_model.iter().next().is_none() {
return window::close(self.main_window_id()); return window::close(self.main_window_id());
} }
return Command::batch([self.update_title(), self.update_watcher()]);
} }
Message::TabMessage(entity_opt, tab_message) => { Message::Save => {
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active()); if !self.filename.is_empty() {
if let Location::Path(tab_path) = &self.tab.location {
let path = tab_path.join(&self.filename);
if path.exists() {
//TODO: dialog or something?
log::warn!("{:?} exists", path);
}
self.result_opt = Some(DialogResult::Open(vec![path]));
return window::close(self.main_window_id());
}
}
}
Message::TabMessage(tab_message) => {
let click_i_opt = match tab_message {
tab::Message::Click(click_i_opt) => click_i_opt,
_ => None,
};
let mut update_opt = None; let updated = self.tab.update(tab_message, self.modifiers);
match self.tab_model.data_mut::<Tab>(entity) {
Some(tab) => { // Update filename box when anything is selected
if tab.update(tab_message, self.modifiers) { if self.flags.kind.save() {
update_opt = Some((tab.title(), tab.location.clone())); if let Some(click_i) = click_i_opt {
if let Some(items) = &self.tab.items_opt {
if let Some(item) = items.get(click_i) {
if item.selected {
self.filename = item.name.clone();
}
}
} }
} }
_ => (),
} }
if let Some((tab_title, tab_path)) = update_opt {
self.tab_model.text_set(entity, tab_title); if updated {
return Command::batch([ return Command::batch([self.update_watcher(), self.rescan_tab()]);
self.update_title(),
self.update_watcher(),
self.rescan_tab(entity, tab_path),
]);
} }
} }
Message::TabRescan(entity, items) => match self.tab_model.data_mut::<Tab>(entity) { Message::TabRescan(mut items) => {
Some(tab) => { // Select based on filename
tab.items_opt = Some(items); for item in items.iter_mut() {
item.selected = item.name == self.filename;
} }
_ => (),
}, self.tab.items_opt = Some(items);
// Reset focus on location change
return widget::text_input::focus(self.filename_id.clone());
}
} }
Command::none() Command::none()
@ -520,44 +527,34 @@ impl Application for App {
fn view(&self) -> Element<Message> { fn view(&self) -> Element<Message> {
let cosmic_theme::Spacing { space_xxs, .. } = self.core().system_theme().cosmic().spacing; let cosmic_theme::Spacing { space_xxs, .. } = self.core().system_theme().cosmic().spacing;
let mut tab_column = widget::column::with_capacity(1); let mut tab_column = widget::column::with_capacity(2);
if self.tab_model.iter().count() > 1 { tab_column = tab_column.push(
tab_column = tab_column.push( self.tab
widget::container( .view(self.core())
widget::view_switcher::horizontal(&self.tab_model) .map(move |message| Message::TabMessage(message)),
.button_height(32) );
.button_spacing(space_xxs)
.on_activate(Message::TabActivate)
.on_close(|entity| Message::TabClose(Some(entity))),
)
.style(style::Container::Background)
.width(Length::Fill),
);
}
let entity = self.tab_model.active();
match self.tab_model.data::<Tab>(entity) {
Some(tab) => {
tab_column = tab_column.push(
tab.view(self.core())
.map(move |message| Message::TabMessage(Some(entity), message)),
);
}
None => {
//TODO
}
}
tab_column = tab_column.push( tab_column = tab_column.push(
widget::row::with_children(vec![ widget::row::with_children(vec![
widget::horizontal_space(Length::Fill).into(), if self.flags.kind.save() {
widget::text_input("", &self.filename)
.id(self.filename_id.clone())
.on_input(Message::Filename)
.on_submit(Message::Save)
.into()
} else {
widget::horizontal_space(Length::Fill).into()
},
widget::button(widget::text(fl!("cancel"))) widget::button(widget::text(fl!("cancel")))
.on_press(Message::Cancel) .on_press(Message::Cancel)
.into(), .into(),
widget::button(widget::text(fl!("open"))) if self.flags.kind.save() {
.on_press(Message::Open) widget::button(widget::text(fl!("save"))).on_press(Message::Save)
.into(), } else {
widget::button(widget::text(fl!("open"))).on_press(Message::Open)
}
.into(),
]) ])
.padding(space_xxs) .padding(space_xxs)
.spacing(space_xxs), .spacing(space_xxs),

View file

@ -23,7 +23,7 @@ use std::{
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use crate::{config::TabConfig, fl, mime_icon::mime_icon}; use crate::{config::TabConfig, dialog::DialogKind, fl, mime_icon::mime_icon};
const DOUBLE_CLICK_DURATION: Duration = Duration::from_millis(500); const DOUBLE_CLICK_DURATION: Duration = Duration::from_millis(500);
//TODO: configurable //TODO: configurable
@ -522,7 +522,7 @@ pub struct Tab {
pub context_menu: Option<Point>, pub context_menu: Option<Point>,
pub items_opt: Option<Vec<Item>>, pub items_opt: Option<Vec<Item>>,
pub view: View, pub view: View,
pub dialog: bool, pub dialog: Option<DialogKind>,
pub edit_location: Option<Location>, pub edit_location: Option<Location>,
pub history_i: usize, pub history_i: usize,
pub history: Vec<Location>, pub history: Vec<Location>,
@ -537,7 +537,7 @@ impl Tab {
context_menu: None, context_menu: None,
items_opt: None, items_opt: None,
view: View::List, view: View::List,
dialog: false, dialog: None,
edit_location: None, edit_location: None,
history_i: 0, history_i: 0,
history, history,
@ -572,7 +572,7 @@ impl Tab {
Location::Path(_) => { Location::Path(_) => {
if item.path.is_dir() { if item.path.is_dir() {
cd = Some(Location::Path(item.path.clone())); cd = Some(Location::Path(item.path.clone()));
} else if !self.dialog { } else if !self.dialog.is_some() {
let mut command = open_command(&item.path); let mut command = open_command(&item.path);
match command.spawn() { match command.spawn() {
Ok(_) => (), Ok(_) => (),
@ -594,7 +594,9 @@ impl Tab {
} }
//TODO: prevent triple-click and beyond from opening file? //TODO: prevent triple-click and beyond from opening file?
item.click_time = Some(Instant::now()); item.click_time = Some(Instant::now());
} else if modifiers.contains(Modifiers::CTRL) { } else if modifiers.contains(Modifiers::CTRL)
&& self.dialog.map_or(true, |x| x.multiple())
{
// Holding control allows multiple selection // Holding control allows multiple selection
item.click_time = None; item.click_time = None;
} else { } else {
@ -646,7 +648,9 @@ impl Tab {
for (i, item) in items.iter_mut().enumerate() { for (i, item) in items.iter_mut().enumerate() {
if i == click_i { if i == click_i {
item.selected = true; item.selected = true;
} else if modifiers.contains(Modifiers::CTRL) { } else if modifiers.contains(Modifiers::CTRL)
&& self.dialog.map_or(true, |x| x.multiple())
{
// Holding control allows multiple selection // Holding control allows multiple selection
} else { } else {
item.selected = false; item.selected = false;
@ -934,9 +938,7 @@ impl Tab {
widget::text::heading(fl!("modified")) widget::text::heading(fl!("modified"))
.width(modified_width) .width(modified_width)
.into(), .into(),
widget::text::heading(fl!("size")) widget::text::heading(fl!("size")).width(size_width).into(),
.width(size_width)
.into(),
]) ])
.align_items(Alignment::Center) .align_items(Alignment::Center)
.padding(space_xxs) .padding(space_xxs)
@ -994,7 +996,7 @@ impl Tab {
//TODO: align columns //TODO: align columns
let button = widget::button( let button = widget::button(
widget::row::with_children(vec![ widget::row::with_children(vec![
if self.dialog { if self.dialog.is_some() {
widget::icon::icon(item.icon_handle_dialog.clone()) widget::icon::icon(item.icon_handle_dialog.clone())
.size(ICON_SIZE_DIALOG) .size(ICON_SIZE_DIALOG)
.into() .into()