Add dialog types
This commit is contained in:
parent
91c4985d42
commit
a24983ca7f
4 changed files with 244 additions and 211 deletions
|
|
@ -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 => {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
383
src/dialog.rs
383
src/dialog.rs
|
|
@ -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),
|
||||||
|
|
|
||||||
22
src/tab.rs
22
src/tab.rs
|
|
@ -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()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue