Add dialog example
This commit is contained in:
parent
87ad2f7dd9
commit
d271159c07
5 changed files with 562 additions and 22 deletions
3
examples/dialog.rs
Normal file
3
examples/dialog.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
cosmic_files::dialog()
|
||||||
|
}
|
||||||
|
|
@ -94,7 +94,6 @@ impl Action {
|
||||||
/// Messages that are used specifically by our [`App`].
|
/// Messages that are used specifically by our [`App`].
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
Todo,
|
|
||||||
AppTheme(AppTheme),
|
AppTheme(AppTheme),
|
||||||
Config(Config),
|
Config(Config),
|
||||||
Copy(Option<segmented_button::Entity>),
|
Copy(Option<segmented_button::Entity>),
|
||||||
|
|
@ -541,9 +540,6 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
Message::Todo => {
|
|
||||||
log::warn!("TODO");
|
|
||||||
}
|
|
||||||
Message::AppTheme(app_theme) => {
|
Message::AppTheme(app_theme) => {
|
||||||
config_set!(app_theme, app_theme);
|
config_set!(app_theme, app_theme);
|
||||||
return self.update_config();
|
return self.update_config();
|
||||||
|
|
|
||||||
502
src/dialog.rs
Normal file
502
src/dialog.rs
Normal file
|
|
@ -0,0 +1,502 @@
|
||||||
|
// Copyright 2023 System76 <inflist_o@system76.com>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
use cosmic::{
|
||||||
|
app::{message, Command, Core},
|
||||||
|
cosmic_theme, executor,
|
||||||
|
iced::{
|
||||||
|
event,
|
||||||
|
futures::{self, SinkExt},
|
||||||
|
keyboard::{Event as KeyEvent, Modifiers},
|
||||||
|
subscription::{self, Subscription},
|
||||||
|
window, Event, Length,
|
||||||
|
},
|
||||||
|
style,
|
||||||
|
widget::{self, segmented_button},
|
||||||
|
Application, ApplicationExt, Element,
|
||||||
|
};
|
||||||
|
use notify::Watcher;
|
||||||
|
use std::{any::TypeId, collections::HashSet, path::PathBuf, time};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
fl, home_dir,
|
||||||
|
tab::{self, Location, Tab},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Flags {}
|
||||||
|
|
||||||
|
/// Messages that are used specifically by our [`App`].
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Message {
|
||||||
|
Modifiers(Modifiers),
|
||||||
|
NotifyEvent(notify::Event),
|
||||||
|
NotifyWatcher(WatcherWrapper),
|
||||||
|
SelectAll(Option<segmented_button::Entity>),
|
||||||
|
TabActivate(segmented_button::Entity),
|
||||||
|
TabClose(Option<segmented_button::Entity>),
|
||||||
|
TabMessage(Option<segmented_button::Entity>, tab::Message),
|
||||||
|
TabRescan(segmented_button::Entity, Vec<tab::Item>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct WatcherWrapper {
|
||||||
|
watcher_opt: Option<notify::RecommendedWatcher>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for WatcherWrapper {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self { watcher_opt: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for WatcherWrapper {
|
||||||
|
fn eq(&self, _other: &Self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The [`App`] stores application-specific state.
|
||||||
|
pub struct App {
|
||||||
|
core: Core,
|
||||||
|
nav_model: segmented_button::SingleSelectModel,
|
||||||
|
tab_model: segmented_button::Model<segmented_button::SingleSelect>,
|
||||||
|
modifiers: Modifiers,
|
||||||
|
watcher_opt: Option<(notify::RecommendedWatcher, HashSet<PathBuf>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
fn open_tab(&mut self, location: Location) -> Command<Message> {
|
||||||
|
let mut tab = Tab::new(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(
|
||||||
|
async move {
|
||||||
|
match tokio::task::spawn_blocking(move || location.scan()).await {
|
||||||
|
Ok(items) => message::app(Message::TabRescan(entity, items)),
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!("failed to rescan: {}", err);
|
||||||
|
message::none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|x| x,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_title(&mut self) -> Command<Message> {
|
||||||
|
let (header_title, window_title) = match self.tab_model.text(self.tab_model.active()) {
|
||||||
|
Some(tab_title) => (
|
||||||
|
tab_title.to_string(),
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_watcher(&mut self) -> Command<Message> {
|
||||||
|
if let Some((mut watcher, old_paths)) = self.watcher_opt.take() {
|
||||||
|
let mut new_paths = HashSet::new();
|
||||||
|
for entity in self.tab_model.iter() {
|
||||||
|
if let Some(tab) = self.tab_model.data::<Tab>(entity) {
|
||||||
|
if let Location::Path(path) = &tab.location {
|
||||||
|
new_paths.insert(path.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwatch paths no longer used
|
||||||
|
for path in old_paths.iter() {
|
||||||
|
if !new_paths.contains(path) {
|
||||||
|
match watcher.unwatch(path) {
|
||||||
|
Ok(()) => {
|
||||||
|
log::debug!("unwatching {:?}", path);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::debug!("failed to unwatch {:?}: {}", path, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch new paths
|
||||||
|
for path in new_paths.iter() {
|
||||||
|
if !old_paths.contains(path) {
|
||||||
|
//TODO: should this be recursive?
|
||||||
|
match watcher.watch(path, notify::RecursiveMode::NonRecursive) {
|
||||||
|
Ok(()) => {
|
||||||
|
log::debug!("watching {:?}", path);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::debug!("failed to watch {:?}: {}", path, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.watcher_opt = Some((watcher, new_paths));
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: should any of this run in a command?
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement [`Application`] to integrate with COSMIC.
|
||||||
|
impl Application for App {
|
||||||
|
/// Default async executor to use with the app.
|
||||||
|
type Executor = executor::Default;
|
||||||
|
|
||||||
|
/// Argument received
|
||||||
|
type Flags = Flags;
|
||||||
|
|
||||||
|
/// Message type specific to our [`App`].
|
||||||
|
type Message = Message;
|
||||||
|
|
||||||
|
/// The unique application ID to supply to the window manager.
|
||||||
|
const APP_ID: &'static str = "com.system76.CosmicFiles";
|
||||||
|
|
||||||
|
fn core(&self) -> &Core {
|
||||||
|
&self.core
|
||||||
|
}
|
||||||
|
|
||||||
|
fn core_mut(&mut self) -> &mut Core {
|
||||||
|
&mut self.core
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates the application, and optionally emits command on initialize.
|
||||||
|
fn init(mut core: Core, flags: Self::Flags) -> (Self, Command<Self::Message>) {
|
||||||
|
core.window.show_maximize = false;
|
||||||
|
core.window.show_minimize = false;
|
||||||
|
|
||||||
|
let mut nav_model = segmented_button::ModelBuilder::default();
|
||||||
|
if let Some(dir) = dirs::home_dir() {
|
||||||
|
nav_model = nav_model.insert(move |b| {
|
||||||
|
b.text(fl!("home"))
|
||||||
|
.icon(widget::icon::icon(tab::folder_icon_symbolic(&dir, 16)).size(16))
|
||||||
|
.data(Location::Path(dir.clone()))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//TODO: Sort by name?
|
||||||
|
for dir_opt in &[
|
||||||
|
dirs::document_dir(),
|
||||||
|
dirs::download_dir(),
|
||||||
|
dirs::audio_dir(),
|
||||||
|
dirs::picture_dir(),
|
||||||
|
dirs::video_dir(),
|
||||||
|
] {
|
||||||
|
if let Some(dir) = dir_opt {
|
||||||
|
if let Some(file_name) = dir.file_name().and_then(|x| x.to_str()) {
|
||||||
|
nav_model = nav_model.insert(move |b| {
|
||||||
|
b.text(file_name.to_string())
|
||||||
|
.icon(widget::icon::icon(tab::folder_icon_symbolic(&dir, 16)).size(16))
|
||||||
|
.data(Location::Path(dir.clone()))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut app = App {
|
||||||
|
core,
|
||||||
|
nav_model: nav_model.build(),
|
||||||
|
tab_model: segmented_button::ModelBuilder::default().build(),
|
||||||
|
modifiers: Modifiers::empty(),
|
||||||
|
watcher_opt: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut commands = Vec::new();
|
||||||
|
|
||||||
|
if app.tab_model.iter().next().is_none() {
|
||||||
|
commands.push(app.open_tab(Location::Path(home_dir())));
|
||||||
|
}
|
||||||
|
|
||||||
|
(app, Command::batch(commands))
|
||||||
|
}
|
||||||
|
|
||||||
|
// The default nav_bar widget needs to have its width reduced for cosmic-files
|
||||||
|
fn nav_bar(&self) -> Option<Element<message::Message<Self::Message>>> {
|
||||||
|
if !self.core().nav_bar_active() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let nav_model = self.nav_model()?;
|
||||||
|
|
||||||
|
let mut nav = widget::nav_bar(nav_model, |entity| {
|
||||||
|
message::cosmic(cosmic::app::cosmic::Message::NavBar(entity))
|
||||||
|
});
|
||||||
|
|
||||||
|
if !self.core().is_condensed() {
|
||||||
|
nav = nav.max_width(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Element::from(nav))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nav_model(&self) -> Option<&segmented_button::SingleSelectModel> {
|
||||||
|
Some(&self.nav_model)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_nav_select(&mut self, entity: segmented_button::Entity) -> Command<Self::Message> {
|
||||||
|
let location_opt = self.nav_model.data::<Location>(entity).clone();
|
||||||
|
|
||||||
|
if let Some(location) = location_opt {
|
||||||
|
let message = Message::TabMessage(None, tab::Message::Location(location.clone()));
|
||||||
|
return self.update(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle application events here.
|
||||||
|
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
|
||||||
|
match message {
|
||||||
|
Message::Modifiers(modifiers) => {
|
||||||
|
self.modifiers = modifiers;
|
||||||
|
}
|
||||||
|
Message::NotifyEvent(event) => {
|
||||||
|
log::debug!("{:?}", event);
|
||||||
|
|
||||||
|
let mut needs_reload = Vec::new();
|
||||||
|
for entity in self.tab_model.iter() {
|
||||||
|
if let Some(tab) = self.tab_model.data::<Tab>(entity) {
|
||||||
|
//TODO: support reloading trash, somehow
|
||||||
|
if let Location::Path(path) = &tab.location {
|
||||||
|
let mut contains_change = false;
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
Some(mut watcher) => {
|
||||||
|
self.watcher_opt = Some((watcher, HashSet::new()));
|
||||||
|
return self.update_watcher();
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
log::warn!("message did not contain notify watcher");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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 item.hidden {
|
||||||
|
//TODO: option to show hidden files
|
||||||
|
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(window::Id::MAIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Command::batch([self.update_title(), self.update_watcher()]);
|
||||||
|
}
|
||||||
|
Message::TabMessage(entity_opt, tab_message) => {
|
||||||
|
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
||||||
|
|
||||||
|
let mut update_opt = None;
|
||||||
|
match self.tab_model.data_mut::<Tab>(entity) {
|
||||||
|
Some(tab) => {
|
||||||
|
if tab.update(tab_message, self.modifiers) {
|
||||||
|
update_opt = Some((tab.title(), tab.location.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
if let Some((tab_title, tab_path)) = update_opt {
|
||||||
|
self.tab_model.text_set(entity, tab_title);
|
||||||
|
return Command::batch([
|
||||||
|
self.update_title(),
|
||||||
|
self.update_watcher(),
|
||||||
|
self.rescan_tab(entity, tab_path),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::TabRescan(entity, items) => match self.tab_model.data_mut::<Tab>(entity) {
|
||||||
|
Some(tab) => {
|
||||||
|
tab.items_opt = Some(items);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a view after each update.
|
||||||
|
fn view(&self) -> Element<Self::Message> {
|
||||||
|
let cosmic_theme::Spacing { space_xxs, .. } = self.core().system_theme().cosmic().spacing;
|
||||||
|
|
||||||
|
let mut tab_column = widget::column::with_capacity(1);
|
||||||
|
|
||||||
|
if self.tab_model.iter().count() > 1 {
|
||||||
|
tab_column = tab_column.push(
|
||||||
|
widget::container(
|
||||||
|
widget::view_switcher::horizontal(&self.tab_model)
|
||||||
|
.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.list_view(self.core())
|
||||||
|
.map(move |message| Message::TabMessage(Some(entity), message)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let content: Element<_> = tab_column.into();
|
||||||
|
|
||||||
|
// Uncomment to debug layout:
|
||||||
|
//content.explain(cosmic::iced::Color::WHITE)
|
||||||
|
content
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subscription(&self) -> Subscription<Self::Message> {
|
||||||
|
struct WatcherSubscription;
|
||||||
|
|
||||||
|
Subscription::batch([
|
||||||
|
event::listen_with(|event, _status| match event {
|
||||||
|
Event::Keyboard(KeyEvent::ModifiersChanged(modifiers)) => {
|
||||||
|
Some(Message::Modifiers(modifiers))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
subscription::channel(
|
||||||
|
TypeId::of::<WatcherSubscription>(),
|
||||||
|
100,
|
||||||
|
|mut output| async move {
|
||||||
|
let watcher_res = {
|
||||||
|
let mut output = output.clone();
|
||||||
|
//TODO: debounce
|
||||||
|
notify::recommended_watcher(
|
||||||
|
move |event_res: Result<notify::Event, notify::Error>| match event_res {
|
||||||
|
Ok(event) => {
|
||||||
|
match &event.kind {
|
||||||
|
notify::EventKind::Access(_)
|
||||||
|
| notify::EventKind::Modify(
|
||||||
|
notify::event::ModifyKind::Metadata(_),
|
||||||
|
) => {
|
||||||
|
// Data not mutated
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
match futures::executor::block_on(async {
|
||||||
|
output.send(Message::NotifyEvent(event)).await
|
||||||
|
}) {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!("failed to send notify event: {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!("failed to watch files: {:?}", err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
match watcher_res {
|
||||||
|
Ok(watcher) => {
|
||||||
|
match output
|
||||||
|
.send(Message::NotifyWatcher(WatcherWrapper {
|
||||||
|
watcher_opt: Some(watcher),
|
||||||
|
}))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!("failed to send notify watcher: {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!("failed to create file watcher: {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: how to properly kill this task?
|
||||||
|
loop {
|
||||||
|
tokio::time::sleep(time::Duration::new(1, 0)).await;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/lib.rs
19
src/lib.rs
|
|
@ -11,6 +11,7 @@ use app::{App, Flags};
|
||||||
mod app;
|
mod app;
|
||||||
use config::{Config, CONFIG_VERSION};
|
use config::{Config, CONFIG_VERSION};
|
||||||
mod config;
|
mod config;
|
||||||
|
mod dialog;
|
||||||
mod key_bind;
|
mod key_bind;
|
||||||
mod localize;
|
mod localize;
|
||||||
mod menu;
|
mod menu;
|
||||||
|
|
@ -29,6 +30,24 @@ pub fn home_dir() -> PathBuf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Runs application with these settings
|
||||||
|
pub fn dialog() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
localize::localize();
|
||||||
|
|
||||||
|
let mut settings = Settings::default();
|
||||||
|
|
||||||
|
#[cfg(target_os = "redox")]
|
||||||
|
{
|
||||||
|
// Redox does not support resize if doing CSDs
|
||||||
|
settings = settings.client_decorations(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let flags = dialog::Flags {};
|
||||||
|
cosmic::app::run::<dialog::App>(settings, flags)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Runs application with these settings
|
/// Runs application with these settings
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
|
||||||
56
src/tab.rs
56
src/tab.rs
|
|
@ -27,6 +27,7 @@ use crate::{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
|
||||||
|
const ICON_SIZE_DIALOG: u16 = 16;
|
||||||
const ICON_SIZE_LIST: u16 = 32;
|
const ICON_SIZE_LIST: u16 = 32;
|
||||||
const ICON_SIZE_GRID: u16 = 64;
|
const ICON_SIZE_GRID: u16 = 64;
|
||||||
static SPECIAL_DIRS: Lazy<HashMap<PathBuf, &'static str>> = Lazy::new(|| {
|
static SPECIAL_DIRS: Lazy<HashMap<PathBuf, &'static str>> = Lazy::new(|| {
|
||||||
|
|
@ -228,13 +229,16 @@ pub fn scan_path(tab_path: &PathBuf) -> Vec<Item> {
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
|
|
||||||
//TODO: configurable size
|
//TODO: configurable size
|
||||||
let (icon_handle_grid, icon_handle_list) = if metadata.is_dir() {
|
let (icon_handle_dialog, icon_handle_grid, icon_handle_list) = if metadata.is_dir()
|
||||||
|
{
|
||||||
(
|
(
|
||||||
|
folder_icon(&path, ICON_SIZE_DIALOG),
|
||||||
folder_icon(&path, ICON_SIZE_GRID),
|
folder_icon(&path, ICON_SIZE_GRID),
|
||||||
folder_icon(&path, ICON_SIZE_LIST),
|
folder_icon(&path, ICON_SIZE_LIST),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
|
mime_icon(&path, ICON_SIZE_DIALOG),
|
||||||
mime_icon(&path, ICON_SIZE_GRID),
|
mime_icon(&path, ICON_SIZE_GRID),
|
||||||
mime_icon(&path, ICON_SIZE_LIST),
|
mime_icon(&path, ICON_SIZE_LIST),
|
||||||
)
|
)
|
||||||
|
|
@ -258,6 +262,7 @@ pub fn scan_path(tab_path: &PathBuf) -> Vec<Item> {
|
||||||
metadata: ItemMetadata::Path { metadata, children },
|
metadata: ItemMetadata::Path { metadata, children },
|
||||||
hidden,
|
hidden,
|
||||||
path,
|
path,
|
||||||
|
icon_handle_dialog,
|
||||||
icon_handle_grid,
|
icon_handle_grid,
|
||||||
icon_handle_list,
|
icon_handle_list,
|
||||||
selected: false,
|
selected: false,
|
||||||
|
|
@ -319,12 +324,14 @@ pub fn scan_trash() -> Vec<Item> {
|
||||||
let name = entry.name.clone();
|
let name = entry.name.clone();
|
||||||
|
|
||||||
//TODO: configurable size
|
//TODO: configurable size
|
||||||
let (icon_handle_grid, icon_handle_list) = match metadata.size {
|
let (icon_handle_dialog, icon_handle_grid, icon_handle_list) = match metadata.size {
|
||||||
trash::TrashItemSize::Entries(_) => (
|
trash::TrashItemSize::Entries(_) => (
|
||||||
|
folder_icon(&path, ICON_SIZE_DIALOG),
|
||||||
folder_icon(&path, ICON_SIZE_GRID),
|
folder_icon(&path, ICON_SIZE_GRID),
|
||||||
folder_icon(&path, ICON_SIZE_LIST),
|
folder_icon(&path, ICON_SIZE_LIST),
|
||||||
),
|
),
|
||||||
trash::TrashItemSize::Bytes(_) => (
|
trash::TrashItemSize::Bytes(_) => (
|
||||||
|
mime_icon(&path, ICON_SIZE_DIALOG),
|
||||||
mime_icon(&path, ICON_SIZE_GRID),
|
mime_icon(&path, ICON_SIZE_GRID),
|
||||||
mime_icon(&path, ICON_SIZE_LIST),
|
mime_icon(&path, ICON_SIZE_LIST),
|
||||||
),
|
),
|
||||||
|
|
@ -335,6 +342,7 @@ pub fn scan_trash() -> Vec<Item> {
|
||||||
metadata: ItemMetadata::Trash { metadata, entry },
|
metadata: ItemMetadata::Trash { metadata, entry },
|
||||||
hidden: false,
|
hidden: false,
|
||||||
path,
|
path,
|
||||||
|
icon_handle_dialog,
|
||||||
icon_handle_grid,
|
icon_handle_grid,
|
||||||
icon_handle_list,
|
icon_handle_list,
|
||||||
selected: false,
|
selected: false,
|
||||||
|
|
@ -410,6 +418,7 @@ pub struct Item {
|
||||||
pub metadata: ItemMetadata,
|
pub metadata: ItemMetadata,
|
||||||
pub hidden: bool,
|
pub hidden: bool,
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
pub icon_handle_dialog: widget::icon::Handle,
|
||||||
pub icon_handle_grid: widget::icon::Handle,
|
pub icon_handle_grid: widget::icon::Handle,
|
||||||
pub icon_handle_list: widget::icon::Handle,
|
pub icon_handle_list: widget::icon::Handle,
|
||||||
pub selected: bool,
|
pub selected: bool,
|
||||||
|
|
@ -507,10 +516,10 @@ pub enum View {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Tab {
|
pub struct Tab {
|
||||||
pub location: Location,
|
pub location: Location,
|
||||||
//TODO
|
|
||||||
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 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>,
|
||||||
|
|
@ -524,6 +533,7 @@ impl Tab {
|
||||||
context_menu: None,
|
context_menu: None,
|
||||||
items_opt: None,
|
items_opt: None,
|
||||||
view: View::List,
|
view: View::List,
|
||||||
|
dialog: false,
|
||||||
edit_location: None,
|
edit_location: None,
|
||||||
history_i: 0,
|
history_i: 0,
|
||||||
history,
|
history,
|
||||||
|
|
@ -879,11 +889,12 @@ impl Tab {
|
||||||
return self.empty_view(hidden > 0, core);
|
return self.empty_view(hidden > 0, core);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
widget::scrollable(widget::column::with_children(vec![
|
widget::column::with_children(vec![
|
||||||
self.location_view(core),
|
self.location_view(core),
|
||||||
widget::flex_row(children).into(),
|
widget::scrollable(widget::flex_row(children))
|
||||||
]))
|
.width(Length::Fill)
|
||||||
.width(Length::Fill)
|
.into(),
|
||||||
|
])
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -895,8 +906,6 @@ impl Tab {
|
||||||
|
|
||||||
let mut children: Vec<Element<_>> = Vec::new();
|
let mut children: Vec<Element<_>> = Vec::new();
|
||||||
|
|
||||||
children.push(self.location_view(core));
|
|
||||||
|
|
||||||
children.push(
|
children.push(
|
||||||
widget::row::with_children(vec![
|
widget::row::with_children(vec![
|
||||||
widget::text::heading(fl!("name"))
|
widget::text::heading(fl!("name"))
|
||||||
|
|
@ -966,9 +975,15 @@ 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![
|
||||||
widget::icon::icon(item.icon_handle_list.clone())
|
if self.dialog {
|
||||||
.size(ICON_SIZE_LIST)
|
widget::icon::icon(item.icon_handle_dialog.clone())
|
||||||
.into(),
|
.size(ICON_SIZE_DIALOG)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
widget::icon::icon(item.icon_handle_list.clone())
|
||||||
|
.size(ICON_SIZE_LIST)
|
||||||
|
.into()
|
||||||
|
},
|
||||||
widget::text(item.name.clone()).width(Length::Fill).into(),
|
widget::text(item.name.clone()).width(Length::Fill).into(),
|
||||||
widget::text(modified_text).width(column_width).into(),
|
widget::text(modified_text).width(column_width).into(),
|
||||||
widget::text(size_text).width(column_width).into(),
|
widget::text(size_text).width(column_width).into(),
|
||||||
|
|
@ -994,12 +1009,17 @@ impl Tab {
|
||||||
return self.empty_view(hidden > 0, core);
|
return self.empty_view(hidden > 0, core);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
widget::scrollable(
|
|
||||||
widget::column::with_children(children)
|
widget::column::with_children(vec![
|
||||||
// Hack to make room for scroll bar
|
self.location_view(core).into(),
|
||||||
.padding([0, space_xxs, 0, 0]),
|
widget::scrollable(
|
||||||
)
|
widget::column::with_children(children)
|
||||||
.width(Length::Fill)
|
// Hack to make room for scroll bar
|
||||||
|
.padding([0, space_xxs, 0, 0]),
|
||||||
|
)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.into(),
|
||||||
|
])
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue