2026-01-09 07:18:43 -06:00
|
|
|
mod file;
|
|
|
|
|
|
|
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
2026-01-15 07:02:21 -06:00
|
|
|
use iced::padding::bottom;
|
2026-01-10 08:02:21 -06:00
|
|
|
use iced::widget::text_editor::{Binding, KeyPress};
|
2026-01-21 07:12:20 -06:00
|
|
|
use iced::widget::{
|
|
|
|
|
button, column, container, markdown, pick_list, row, scrollable, text, text_editor,
|
|
|
|
|
};
|
2026-01-09 07:51:53 -06:00
|
|
|
use iced::window::icon;
|
2026-01-21 07:12:20 -06:00
|
|
|
use iced::{Background, Border, Element, Task, border};
|
2026-01-10 08:02:21 -06:00
|
|
|
use iced::{Font, Length, Theme};
|
|
|
|
|
use iced::{keyboard, window};
|
2026-01-06 07:19:44 -06:00
|
|
|
use rfd::FileDialog;
|
2026-01-11 08:04:25 -06:00
|
|
|
|
2026-01-09 07:18:43 -06:00
|
|
|
const CUSTOM_FONT: Font = Font::with_name("CaskaydiaCove Nerd Font Mono");
|
2026-01-11 08:04:25 -06:00
|
|
|
const DEFAULT_EDITOR_FONT_SIZE: u32 = 16;
|
2026-01-10 08:02:21 -06:00
|
|
|
const MAX_EDITOR_FONT_SIZE: u32 = 80;
|
|
|
|
|
const MIN_EDITOR_FONT_SIZE: u32 = 12;
|
2026-01-09 07:18:43 -06:00
|
|
|
|
2026-01-15 07:02:21 -06:00
|
|
|
// TODO: Need to handle that when user
|
|
|
|
|
// cancels file action it is causing
|
|
|
|
|
// the currently selected path to be
|
|
|
|
|
// emptied out
|
|
|
|
|
|
2026-01-11 08:04:25 -06:00
|
|
|
// TODO: Need to reconsider the way
|
|
|
|
|
// we are holding on to files. Maybe
|
|
|
|
|
// vec is not best
|
|
|
|
|
|
|
|
|
|
// TODO: Need to handle having more tabs
|
|
|
|
|
// then can be displayed in the container
|
|
|
|
|
// given it's current width
|
|
|
|
|
|
|
|
|
|
// TODO: We need to style the button tab
|
|
|
|
|
// according to which is currently focused
|
|
|
|
|
// in the editor
|
|
|
|
|
|
|
|
|
|
// TODO: Handle creating new files
|
|
|
|
|
|
|
|
|
|
// TODO: Opening another window
|
2026-01-15 07:02:21 -06:00
|
|
|
struct File {
|
|
|
|
|
content: text_editor::Content,
|
|
|
|
|
path: Option<PathBuf>,
|
2026-01-21 07:12:20 -06:00
|
|
|
markdown: Vec<markdown::Item>,
|
2026-01-15 07:02:21 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for File {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
File {
|
|
|
|
|
content: text_editor::Content::new(),
|
|
|
|
|
path: None,
|
2026-01-21 07:12:20 -06:00
|
|
|
markdown: Vec::new(),
|
2026-01-15 07:02:21 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 07:12:20 -06:00
|
|
|
#[derive(Default)]
|
|
|
|
|
enum Mode {
|
|
|
|
|
#[default]
|
|
|
|
|
Edit,
|
|
|
|
|
Preview,
|
|
|
|
|
}
|
2026-01-15 07:02:21 -06:00
|
|
|
|
2026-01-06 07:19:44 -06:00
|
|
|
#[derive(Default)]
|
|
|
|
|
struct State {
|
2026-01-21 07:12:20 -06:00
|
|
|
mode: Mode,
|
2026-01-18 08:33:49 -06:00
|
|
|
files: Vec<File>,
|
|
|
|
|
current_file: usize,
|
2026-01-10 08:02:21 -06:00
|
|
|
editor_font_size: u32,
|
2026-01-06 07:19:44 -06:00
|
|
|
selected_file_action: Option<FileAction>,
|
2026-01-11 08:04:25 -06:00
|
|
|
selected_view_action: Option<ViewAction>,
|
2026-01-06 07:19:44 -06:00
|
|
|
}
|
|
|
|
|
|
2026-01-15 07:02:21 -06:00
|
|
|
// fn get_current_file(mut files: &HashMap<Uuid, File>, current_file: &Uuid) -> &mut File {
|
|
|
|
|
// files
|
|
|
|
|
// .get_mut(current_file)
|
|
|
|
|
// .expect("current file id not present in files map")
|
|
|
|
|
// }
|
|
|
|
|
|
2026-01-06 07:19:44 -06:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
|
|
enum FileAction {
|
2026-01-15 07:02:21 -06:00
|
|
|
New,
|
2026-01-09 07:18:43 -06:00
|
|
|
Save,
|
2026-01-06 07:19:44 -06:00
|
|
|
SaveAs,
|
2026-01-09 07:18:43 -06:00
|
|
|
Open,
|
2026-01-18 08:33:49 -06:00
|
|
|
Close(Option<usize>),
|
2026-01-06 07:19:44 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl FileAction {
|
2026-01-15 07:02:21 -06:00
|
|
|
const ALL: &'static [FileAction] = &[
|
|
|
|
|
FileAction::New,
|
|
|
|
|
FileAction::Save,
|
|
|
|
|
FileAction::SaveAs,
|
|
|
|
|
FileAction::Open,
|
2026-01-18 08:33:49 -06:00
|
|
|
FileAction::Close(None),
|
2026-01-15 07:02:21 -06:00
|
|
|
];
|
2026-01-06 07:19:44 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for FileAction {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
match self {
|
2026-01-15 07:02:21 -06:00
|
|
|
FileAction::New => write!(f, "New file"),
|
2026-01-09 07:18:43 -06:00
|
|
|
FileAction::Save => write!(f, "Save"),
|
2026-01-11 08:04:25 -06:00
|
|
|
FileAction::SaveAs => write!(f, "Save as... "),
|
2026-01-09 07:18:43 -06:00
|
|
|
FileAction::Open => write!(f, "Open"),
|
2026-01-18 08:33:49 -06:00
|
|
|
FileAction::Close(_) => write!(f, "Close"),
|
2026-01-06 07:19:44 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-11 08:04:25 -06:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
|
|
enum ViewAction {
|
2026-01-18 08:33:49 -06:00
|
|
|
Increase,
|
|
|
|
|
Decrease,
|
|
|
|
|
Reset,
|
2026-01-21 07:12:20 -06:00
|
|
|
TogglePreview,
|
2026-01-11 08:04:25 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ViewAction {
|
|
|
|
|
const ALL: &'static [ViewAction] = &[
|
2026-01-18 08:33:49 -06:00
|
|
|
ViewAction::Increase,
|
|
|
|
|
ViewAction::Decrease,
|
|
|
|
|
ViewAction::Reset,
|
2026-01-21 07:12:20 -06:00
|
|
|
ViewAction::TogglePreview,
|
2026-01-11 08:04:25 -06:00
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for ViewAction {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
match self {
|
2026-01-18 08:33:49 -06:00
|
|
|
ViewAction::Decrease => write!(f, "Decrease font"),
|
|
|
|
|
ViewAction::Increase => write!(f, "Increase font"),
|
|
|
|
|
ViewAction::Reset => write!(f, "Reset font"),
|
2026-01-21 07:12:20 -06:00
|
|
|
ViewAction::TogglePreview => write!(f, "Preview"),
|
2026-01-11 08:04:25 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-06 07:19:44 -06:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
enum Message {
|
|
|
|
|
Edit(text_editor::Action),
|
|
|
|
|
FileActionSelected(FileAction),
|
2026-01-11 08:04:25 -06:00
|
|
|
ViewActionSelected(ViewAction),
|
2026-01-18 08:33:49 -06:00
|
|
|
SwitchTab(usize),
|
2026-01-21 07:12:20 -06:00
|
|
|
LinkClicked(String),
|
2026-01-06 07:19:44 -06:00
|
|
|
}
|
|
|
|
|
|
2026-01-21 07:12:20 -06:00
|
|
|
// TODO: Can we at least make
|
|
|
|
|
// theme configurable
|
|
|
|
|
// and then maybe even define
|
|
|
|
|
// complete custom and/or use
|
|
|
|
|
// existing as starter
|
2026-01-09 07:18:43 -06:00
|
|
|
fn theme(_state: &State) -> Theme {
|
|
|
|
|
Theme::Dark
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-06 07:19:44 -06:00
|
|
|
fn view(state: &State) -> Element<'_, Message> {
|
2026-01-11 08:04:25 -06:00
|
|
|
let mut tab_row = row![];
|
|
|
|
|
|
2026-01-18 08:33:49 -06:00
|
|
|
for (file_index, file) in state.files.iter().enumerate() {
|
|
|
|
|
let file_name = if let Some(p) = &file.path {
|
2026-01-15 07:02:21 -06:00
|
|
|
p.file_name()
|
2026-01-13 04:25:46 -06:00
|
|
|
.expect("unable to get file name")
|
|
|
|
|
.to_str()
|
2026-01-15 07:02:21 -06:00
|
|
|
.expect("unable to get file name")
|
|
|
|
|
} else {
|
|
|
|
|
"New file"
|
|
|
|
|
};
|
2026-01-13 04:25:46 -06:00
|
|
|
|
2026-01-18 08:33:49 -06:00
|
|
|
let tab_button_text = text(file_name).wrapping(text::Wrapping::None);
|
|
|
|
|
let delete_button = button(text("x")).on_press(Message::FileActionSelected(
|
|
|
|
|
FileAction::Close(Some(file_index)),
|
|
|
|
|
));
|
2026-01-15 07:02:21 -06:00
|
|
|
|
2026-01-18 08:33:49 -06:00
|
|
|
let tab_button = button(row![tab_button_text, delete_button])
|
|
|
|
|
.style(move |theme: &Theme, status| {
|
2026-01-15 07:02:21 -06:00
|
|
|
let base = button::primary(theme, status);
|
2026-01-18 08:33:49 -06:00
|
|
|
let is_focused = state.current_file == file_index;
|
2026-01-15 07:02:21 -06:00
|
|
|
let button_background = if is_focused {
|
|
|
|
|
base.background
|
|
|
|
|
} else {
|
|
|
|
|
Some(Background::Color(theme.palette().background))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
button::Style {
|
|
|
|
|
background: button_background,
|
|
|
|
|
border: Border {
|
|
|
|
|
radius: border::radius(0).top_left(10).top_right(10),
|
|
|
|
|
..base.border
|
|
|
|
|
},
|
|
|
|
|
..base
|
|
|
|
|
}
|
|
|
|
|
})
|
2026-01-18 08:33:49 -06:00
|
|
|
.on_press(Message::SwitchTab(file_index));
|
2026-01-15 07:02:21 -06:00
|
|
|
|
2026-01-18 08:33:49 -06:00
|
|
|
tab_row = tab_row.push(tab_button);
|
2026-01-11 08:04:25 -06:00
|
|
|
}
|
|
|
|
|
|
2026-01-15 07:02:21 -06:00
|
|
|
let tabs = scrollable(container(tab_row).padding(bottom(10))).direction(
|
|
|
|
|
scrollable::Direction::Horizontal(scrollable::Scrollbar::new()),
|
|
|
|
|
);
|
2026-01-11 08:04:25 -06:00
|
|
|
|
2026-01-06 07:19:44 -06:00
|
|
|
let file_menu = pick_list(
|
|
|
|
|
FileAction::ALL,
|
|
|
|
|
state.selected_file_action,
|
|
|
|
|
Message::FileActionSelected,
|
|
|
|
|
)
|
|
|
|
|
.placeholder("File");
|
|
|
|
|
|
2026-01-11 08:04:25 -06:00
|
|
|
let view_menu = pick_list(
|
|
|
|
|
ViewAction::ALL,
|
|
|
|
|
state.selected_view_action,
|
|
|
|
|
Message::ViewActionSelected,
|
|
|
|
|
)
|
|
|
|
|
.placeholder("View");
|
|
|
|
|
|
|
|
|
|
let action_bar = container(row![file_menu, view_menu].spacing(5));
|
2026-01-09 07:18:43 -06:00
|
|
|
|
2026-01-18 08:33:49 -06:00
|
|
|
let current_file = &state.files[state.current_file];
|
2026-01-15 07:02:21 -06:00
|
|
|
|
|
|
|
|
let editor = text_editor(¤t_file.content)
|
2026-01-10 08:02:21 -06:00
|
|
|
.size(state.editor_font_size)
|
2026-01-09 07:18:43 -06:00
|
|
|
.height(Length::Fill)
|
2026-01-10 08:02:21 -06:00
|
|
|
.on_action(Message::Edit)
|
|
|
|
|
.key_binding(|key_press: KeyPress| {
|
2026-01-15 07:02:21 -06:00
|
|
|
let n = keyboard::Key::Character("n".into());
|
2026-01-10 08:02:21 -06:00
|
|
|
let s = keyboard::Key::Character("s".into());
|
|
|
|
|
let o = keyboard::Key::Character("o".into());
|
2026-01-21 07:12:20 -06:00
|
|
|
let w = keyboard::Key::Character("w".into());
|
2026-01-10 08:02:21 -06:00
|
|
|
let minus = keyboard::Key::Character("-".into());
|
|
|
|
|
let equals = keyboard::Key::Character("=".into());
|
2026-01-11 08:04:25 -06:00
|
|
|
let zero = keyboard::Key::Character("0".into());
|
2026-01-10 08:02:21 -06:00
|
|
|
|
2026-01-15 07:02:21 -06:00
|
|
|
let is_new = key_press.modifiers.command() && key_press.key == n;
|
2026-01-10 08:02:21 -06:00
|
|
|
let is_save = key_press.modifiers.command() && key_press.key == s;
|
|
|
|
|
let is_save_as =
|
|
|
|
|
key_press.modifiers.command() && key_press.modifiers.shift() && key_press.key == s;
|
2026-01-15 07:02:21 -06:00
|
|
|
let is_open = key_press.modifiers.command() && key_press.key == o;
|
2026-01-21 07:12:20 -06:00
|
|
|
let is_close = key_press.modifiers.command() && key_press.key == w;
|
2026-01-18 08:33:49 -06:00
|
|
|
|
2026-01-11 08:04:25 -06:00
|
|
|
let is_increase_font = key_press.modifiers.command() && key_press.key == equals;
|
2026-01-10 08:02:21 -06:00
|
|
|
let is_decrease_font = key_press.modifiers.command() && key_press.key == minus;
|
2026-01-11 08:04:25 -06:00
|
|
|
let is_reset_font = key_press.modifiers.command() && key_press.key == zero;
|
|
|
|
|
|
2026-01-21 07:12:20 -06:00
|
|
|
if is_close {
|
|
|
|
|
return Some(Binding::Custom(Message::FileActionSelected(
|
|
|
|
|
FileAction::Close(Some(state.current_file)),
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-11 08:04:25 -06:00
|
|
|
if is_reset_font {
|
|
|
|
|
return Some(Binding::Custom(Message::ViewActionSelected(
|
2026-01-18 08:33:49 -06:00
|
|
|
ViewAction::Reset,
|
2026-01-11 08:04:25 -06:00
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-10 08:02:21 -06:00
|
|
|
if is_increase_font {
|
2026-01-11 08:04:25 -06:00
|
|
|
return Some(Binding::Custom(Message::ViewActionSelected(
|
2026-01-18 08:33:49 -06:00
|
|
|
ViewAction::Increase,
|
2026-01-11 08:04:25 -06:00
|
|
|
)));
|
2026-01-10 08:02:21 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if is_decrease_font {
|
2026-01-11 08:04:25 -06:00
|
|
|
return Some(Binding::Custom(Message::ViewActionSelected(
|
2026-01-18 08:33:49 -06:00
|
|
|
ViewAction::Decrease,
|
2026-01-11 08:04:25 -06:00
|
|
|
)));
|
2026-01-10 08:02:21 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if is_save_as {
|
|
|
|
|
return Some(Binding::Custom(Message::FileActionSelected(
|
|
|
|
|
FileAction::SaveAs,
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if is_save {
|
|
|
|
|
return Some(Binding::Custom(Message::FileActionSelected(
|
|
|
|
|
FileAction::Save,
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if is_open {
|
|
|
|
|
return Some(Binding::Custom(Message::FileActionSelected(
|
|
|
|
|
FileAction::Open,
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 07:02:21 -06:00
|
|
|
if is_new {
|
|
|
|
|
return Some(Binding::Custom(Message::FileActionSelected(
|
|
|
|
|
FileAction::New,
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-10 08:02:21 -06:00
|
|
|
text_editor::Binding::from_key_press(key_press)
|
|
|
|
|
});
|
2026-01-06 07:19:44 -06:00
|
|
|
|
2026-01-21 07:12:20 -06:00
|
|
|
let mut markdown_styles: markdown::Style = Theme::Dark.into();
|
|
|
|
|
markdown_styles.font = CUSTOM_FONT;
|
|
|
|
|
|
|
|
|
|
let markdown_settings = markdown::Settings::with_text_size(state.editor_font_size, markdown_styles);
|
|
|
|
|
let markdown_preview =
|
|
|
|
|
markdown::view(¤t_file.markdown, markdown_settings).map(Message::LinkClicked);
|
|
|
|
|
|
|
|
|
|
let preview_container = container(row![markdown_preview]).height(Length::Fill);
|
2026-01-09 07:18:43 -06:00
|
|
|
let editor_container = container(row![editor]).height(Length::Fill);
|
|
|
|
|
|
2026-01-15 07:02:21 -06:00
|
|
|
let cursor_position = current_file.content.cursor().position;
|
|
|
|
|
|
2026-01-09 07:18:43 -06:00
|
|
|
let cursor_display_text = format!(
|
|
|
|
|
"Ln {}, Col {}",
|
|
|
|
|
cursor_position.line, cursor_position.column
|
|
|
|
|
);
|
|
|
|
|
let cursor_text = text(cursor_display_text);
|
|
|
|
|
|
2026-01-15 07:02:21 -06:00
|
|
|
let file_path_display_text = match ¤t_file.path {
|
2026-01-09 07:18:43 -06:00
|
|
|
Some(path) => path.to_string_lossy().to_string(),
|
|
|
|
|
None => String::new(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let file_path_text = text(file_path_display_text);
|
|
|
|
|
|
|
|
|
|
let status_bar = container(row![file_path_text, cursor_text].spacing(10));
|
|
|
|
|
|
2026-01-21 07:12:20 -06:00
|
|
|
let main = match state.mode {
|
|
|
|
|
Mode::Edit => editor_container,
|
|
|
|
|
Mode::Preview => preview_container,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
container(column![tabs, action_bar, main, status_bar])
|
2026-01-09 07:18:43 -06:00
|
|
|
.padding(10)
|
|
|
|
|
.into()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn save_file(path: Option<PathBuf>, text: String) -> Option<PathBuf> {
|
|
|
|
|
let mut save_path = path.clone();
|
|
|
|
|
|
2026-01-18 08:33:49 -06:00
|
|
|
if path.is_none() {
|
2026-01-09 07:18:43 -06:00
|
|
|
save_path = FileDialog::new().set_directory("/").save_file();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match save_path {
|
|
|
|
|
Some(p) => {
|
|
|
|
|
let sp = p.clone();
|
|
|
|
|
file::save_file_to_disk(sp, text);
|
|
|
|
|
Some(p)
|
|
|
|
|
}
|
|
|
|
|
None => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-10 08:02:21 -06:00
|
|
|
fn save_file_as(text: String) -> Option<PathBuf> {
|
2026-01-09 07:18:43 -06:00
|
|
|
let files = FileDialog::new().set_directory("/").save_file();
|
|
|
|
|
|
|
|
|
|
match files {
|
2026-01-10 08:02:21 -06:00
|
|
|
Some(path) => {
|
|
|
|
|
file::save_file_to_disk(path.clone(), text);
|
|
|
|
|
Some(path)
|
2026-01-09 07:18:43 -06:00
|
|
|
}
|
2026-01-10 08:02:21 -06:00
|
|
|
None => None,
|
2026-01-09 07:18:43 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 07:02:21 -06:00
|
|
|
fn open_file() -> (Option<PathBuf>, String) {
|
2026-01-09 07:18:43 -06:00
|
|
|
let file = FileDialog::new().set_directory("/").pick_file();
|
|
|
|
|
|
|
|
|
|
match file {
|
|
|
|
|
Some(path) => {
|
|
|
|
|
let content = file::load_file_from_disk(path.clone());
|
2026-01-15 07:02:21 -06:00
|
|
|
(Some(path), content)
|
2026-01-09 07:18:43 -06:00
|
|
|
}
|
2026-01-15 07:02:21 -06:00
|
|
|
None => (None, String::new()),
|
2026-01-09 07:18:43 -06:00
|
|
|
}
|
2026-01-06 07:19:44 -06:00
|
|
|
}
|
|
|
|
|
|
2026-01-21 07:12:20 -06:00
|
|
|
fn update(state: &mut State, message: Message) -> Task<Message> {
|
2026-01-06 07:19:44 -06:00
|
|
|
match message {
|
|
|
|
|
Message::Edit(action) => {
|
2026-01-21 07:12:20 -06:00
|
|
|
// TODO: Probably shouldn't parse every time we edited
|
2026-01-18 08:33:49 -06:00
|
|
|
let current_file = &mut state.files[state.current_file];
|
2026-01-15 07:02:21 -06:00
|
|
|
current_file.content.perform(action);
|
2026-01-21 07:12:20 -06:00
|
|
|
current_file.markdown = markdown::parse(¤t_file.content.text()).collect();
|
2026-01-06 07:19:44 -06:00
|
|
|
}
|
|
|
|
|
Message::FileActionSelected(action) => {
|
|
|
|
|
state.selected_file_action = None;
|
|
|
|
|
|
|
|
|
|
match action {
|
2026-01-13 04:25:46 -06:00
|
|
|
FileAction::SaveAs => {
|
2026-01-18 08:33:49 -06:00
|
|
|
let current_file = &mut state.files[state.current_file];
|
2026-01-15 07:02:21 -06:00
|
|
|
let path = save_file_as(current_file.content.text());
|
|
|
|
|
current_file.path = path;
|
2026-01-13 04:25:46 -06:00
|
|
|
}
|
2026-01-09 07:18:43 -06:00
|
|
|
FileAction::Open => {
|
|
|
|
|
let (path, content) = open_file();
|
2026-01-15 07:02:21 -06:00
|
|
|
|
2026-01-18 08:33:49 -06:00
|
|
|
if let Some(opened_path) = &path {
|
|
|
|
|
for (file_index, file) in state.files.iter_mut().enumerate() {
|
|
|
|
|
if let Some(existing_path) = &file.path
|
|
|
|
|
&& opened_path == existing_path
|
|
|
|
|
{
|
|
|
|
|
file.content = text_editor::Content::with_text(&content);
|
|
|
|
|
state.current_file = file_index;
|
2026-01-21 07:12:20 -06:00
|
|
|
return Task::none();
|
2026-01-15 07:02:21 -06:00
|
|
|
}
|
|
|
|
|
}
|
2026-01-18 08:33:49 -06:00
|
|
|
|
|
|
|
|
let opened_file = File {
|
|
|
|
|
path,
|
|
|
|
|
content: text_editor::Content::with_text(&content),
|
2026-01-21 07:12:20 -06:00
|
|
|
markdown: markdown::parse(&content).collect(),
|
2026-01-18 08:33:49 -06:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
state.files.push(opened_file);
|
|
|
|
|
state.current_file = state.files.len() - 1;
|
2026-01-15 07:02:21 -06:00
|
|
|
}
|
2026-01-09 07:18:43 -06:00
|
|
|
}
|
|
|
|
|
FileAction::Save => {
|
2026-01-18 08:33:49 -06:00
|
|
|
let current_file = &mut state.files[state.current_file];
|
2026-01-15 07:02:21 -06:00
|
|
|
let path = save_file(current_file.path.clone(), current_file.content.text());
|
|
|
|
|
current_file.path = path;
|
|
|
|
|
}
|
|
|
|
|
FileAction::New => {
|
|
|
|
|
let default_file = File::default();
|
|
|
|
|
|
2026-01-18 08:33:49 -06:00
|
|
|
state.files.push(default_file);
|
|
|
|
|
state.current_file = state.files.len() - 1;
|
|
|
|
|
}
|
|
|
|
|
FileAction::Close(idx) => {
|
|
|
|
|
let idx_to_close = match idx {
|
|
|
|
|
Some(i) => i,
|
|
|
|
|
None => state.current_file,
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-21 07:12:20 -06:00
|
|
|
if state.files.len() == 1 {
|
|
|
|
|
return iced::exit();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if state.files.len() - 1 == idx_to_close {
|
|
|
|
|
state.current_file = state.files.len() - 2;
|
|
|
|
|
state.files.remove(idx_to_close);
|
|
|
|
|
return Task::none();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state.current_file -= 1;
|
2026-01-18 08:33:49 -06:00
|
|
|
state.files.remove(idx_to_close);
|
2026-01-06 07:19:44 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-11 08:04:25 -06:00
|
|
|
Message::ViewActionSelected(action) => {
|
|
|
|
|
state.selected_view_action = None;
|
2026-01-10 08:02:21 -06:00
|
|
|
|
2026-01-11 08:04:25 -06:00
|
|
|
match action {
|
2026-01-18 08:33:49 -06:00
|
|
|
ViewAction::Increase => {
|
2026-01-11 08:04:25 -06:00
|
|
|
if state.editor_font_size >= MAX_EDITOR_FONT_SIZE {
|
2026-01-21 07:12:20 -06:00
|
|
|
return Task::none();
|
2026-01-11 08:04:25 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state.editor_font_size += 2;
|
|
|
|
|
}
|
2026-01-18 08:33:49 -06:00
|
|
|
ViewAction::Decrease => {
|
2026-01-11 08:04:25 -06:00
|
|
|
if state.editor_font_size <= MIN_EDITOR_FONT_SIZE {
|
2026-01-21 07:12:20 -06:00
|
|
|
return Task::none();
|
2026-01-11 08:04:25 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state.editor_font_size -= 2;
|
|
|
|
|
}
|
2026-01-21 07:12:20 -06:00
|
|
|
ViewAction::Reset => {
|
|
|
|
|
state.editor_font_size = DEFAULT_EDITOR_FONT_SIZE;
|
|
|
|
|
}
|
|
|
|
|
// TODO: Carry previous mode when toggling
|
|
|
|
|
// so that if we already in preview we can
|
|
|
|
|
// just put the user back where they were
|
|
|
|
|
ViewAction::TogglePreview => match state.mode {
|
|
|
|
|
Mode::Edit => state.mode = Mode::Preview,
|
|
|
|
|
Mode::Preview => state.mode = Mode::Edit,
|
|
|
|
|
},
|
2026-01-11 08:04:25 -06:00
|
|
|
}
|
2026-01-13 04:25:46 -06:00
|
|
|
}
|
2026-01-15 07:02:21 -06:00
|
|
|
Message::SwitchTab(file_id) => {
|
|
|
|
|
state.current_file = file_id;
|
2026-01-13 04:25:46 -06:00
|
|
|
}
|
2026-01-21 07:12:20 -06:00
|
|
|
Message::LinkClicked(link) => {
|
|
|
|
|
print!("Link clicked: {}", link);
|
|
|
|
|
}
|
2026-01-06 07:19:44 -06:00
|
|
|
}
|
2026-01-21 07:12:20 -06:00
|
|
|
|
|
|
|
|
Task::none()
|
2026-01-06 07:19:44 -06:00
|
|
|
}
|
|
|
|
|
|
2026-01-09 07:18:43 -06:00
|
|
|
fn boot() -> State {
|
2026-01-15 07:02:21 -06:00
|
|
|
let default_file = File::default();
|
|
|
|
|
|
2026-01-18 08:33:49 -06:00
|
|
|
let files = vec![default_file];
|
2026-01-15 07:02:21 -06:00
|
|
|
|
2026-01-10 08:02:21 -06:00
|
|
|
State {
|
2026-01-18 08:33:49 -06:00
|
|
|
files,
|
|
|
|
|
current_file: 0,
|
2026-01-11 08:04:25 -06:00
|
|
|
editor_font_size: DEFAULT_EDITOR_FONT_SIZE,
|
2026-01-10 08:02:21 -06:00
|
|
|
..Default::default()
|
|
|
|
|
}
|
2026-01-09 07:18:43 -06:00
|
|
|
}
|
|
|
|
|
|
2026-01-06 07:19:44 -06:00
|
|
|
pub fn main() -> iced::Result {
|
2026-01-09 07:18:43 -06:00
|
|
|
iced::application(boot, update, view)
|
|
|
|
|
.font(include_bytes!("./fonts/CaskaydiaCoveNFM-Regular.ttf"))
|
|
|
|
|
.theme(theme)
|
|
|
|
|
.settings(iced::Settings {
|
|
|
|
|
default_font: CUSTOM_FONT,
|
|
|
|
|
..Default::default()
|
|
|
|
|
})
|
2026-01-09 07:51:53 -06:00
|
|
|
.window(window::Settings {
|
|
|
|
|
icon: Some(
|
|
|
|
|
icon::from_file_data(include_bytes!("./images/icon.ico"), None)
|
|
|
|
|
.expect("Failed to load icon"),
|
|
|
|
|
),
|
|
|
|
|
..window::Settings::default()
|
|
|
|
|
})
|
2026-01-09 07:18:43 -06:00
|
|
|
.run()
|
2026-01-06 07:19:44 -06:00
|
|
|
}
|