diff options
| author | Polesznyák Márk László <116908301+pml68@users.noreply.github.com> | 2024-12-26 00:12:06 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-12-26 00:12:06 +0100 |
| commit | 0ae3ec6cc9babcab39c76f023606229a151916ab (patch) | |
| tree | e92d0109599622984b2c485cc020951da288cec3 /iced_builder/src/main.rs | |
| parent | Merge pull request #3 from pml68/refactor/internal-restructuring (diff) | |
| parent | feat: add `tip` widget helper from `hecrj/icebreaker` (diff) | |
| download | iced-builder-0ae3ec6cc9babcab39c76f023606229a151916ab.tar.gz | |
Merge pull request #4 from pml68/feat/playground
Playground done **for now**
Diffstat (limited to '')
| -rw-r--r-- | iced_builder/src/main.rs | 270 |
1 files changed, 165 insertions, 105 deletions
diff --git a/iced_builder/src/main.rs b/iced_builder/src/main.rs index ed3f264..a041c6f 100644 --- a/iced_builder/src/main.rs +++ b/iced_builder/src/main.rs @@ -1,28 +1,24 @@ use std::path::PathBuf; -use iced::{ - advanced::widget::Id, - clipboard, keyboard, - widget::{ - button, container, - pane_grid::{self, Pane, PaneGrid}, - row, text_editor, Column, - }, - Alignment, Element, Length, Settings, Task, Theme, -}; -use iced_builder::{ - types::{element_name::ElementName, project::Project, rendered_element::Action, DesignerPage}, - views::{code_view, designer_view, element_list}, - Message, +use iced::advanced::widget::Id; +use iced::widget::pane_grid::{self, Pane, PaneGrid}; +use iced::widget::{container, pick_list, row, text_editor, Column}; +use iced::{clipboard, keyboard, Alignment, Element, Length, Task, Theme}; +use iced_anim::{Animation, Spring}; +use iced_builder::dialogs::{error_dialog, unsaved_changes_dialog}; +use iced_builder::icon; +use iced_builder::panes::{code_view, designer_view, element_list}; +use iced_builder::types::{ + Action, DesignerPage, ElementName, Message, Project, }; +use rfd::MessageDialogResult; + +const THEMES: &'static [Theme] = &[Theme::SolarizedDark, Theme::SolarizedLight]; fn main() -> iced::Result { iced::application(App::title, App::update, App::view) - .settings(Settings { - fonts: vec![include_bytes!("../fonts/icons.ttf").as_slice().into()], - ..Settings::default() - }) - .theme(App::theme) + .font(icon::FONT) + .theme(|state| state.theme.value().clone()) .subscription(App::subscription) .run_with(App::new) } @@ -32,11 +28,11 @@ struct App { is_loading: bool, project_path: Option<PathBuf>, project: Project, - dark_theme: bool, + theme: Spring<Theme>, pane_state: pane_grid::State<Panes>, focus: Option<Pane>, designer_page: DesignerPage, - element_list: Vec<ElementName>, + element_list: &'static [ElementName], editor_content: text_editor::Content, } @@ -48,23 +44,25 @@ enum Panes { impl App { fn new() -> (Self, Task<Message>) { - let state = pane_grid::State::with_configuration(pane_grid::Configuration::Split { - axis: pane_grid::Axis::Vertical, - ratio: 0.8, - a: Box::new(pane_grid::Configuration::Pane(Panes::Designer)), - b: Box::new(pane_grid::Configuration::Pane(Panes::ElementList)), - }); + let state = pane_grid::State::with_configuration( + pane_grid::Configuration::Split { + axis: pane_grid::Axis::Vertical, + ratio: 0.8, + a: Box::new(pane_grid::Configuration::Pane(Panes::Designer)), + b: Box::new(pane_grid::Configuration::Pane(Panes::ElementList)), + }, + ); ( Self { is_dirty: false, is_loading: false, project_path: None, project: Project::new(), - dark_theme: true, + theme: Spring::new(Theme::SolarizedDark), pane_state: state, focus: None, - designer_page: DesignerPage::Designer, - element_list: ElementName::ALL.to_vec(), + designer_page: DesignerPage::DesignerView, + element_list: ElementName::ALL, editor_content: text_editor::Content::new(), }, Task::none(), @@ -91,18 +89,14 @@ impl App { format!("iced Builder{project_name}{saved_state}") } - fn theme(&self) -> iced::Theme { - if self.dark_theme { - Theme::SolarizedDark - } else { - Theme::SolarizedLight - } - } - fn update(&mut self, message: Message) -> Task<Message> { match message { - Message::ToggleTheme => self.dark_theme = !self.dark_theme, - Message::CopyCode => return clipboard::write(self.editor_content.text()), + Message::ToggleTheme(event) => { + self.theme.update(event); + } + Message::CopyCode => { + return clipboard::write(self.editor_content.text()) + } Message::SwitchPage(page) => self.designer_page = page, Message::EditorAction(action) => { if let text_editor::Action::Scroll { lines: _ } = action { @@ -110,12 +104,13 @@ impl App { } } Message::RefreshEditorContent => { - let code = self - .project - .clone() - .app_code() - .unwrap_or_else(|err| err.to_string()); - self.editor_content = text_editor::Content::with_text(&code); + match self.project.clone().app_code() { + Ok(code) => { + self.editor_content = + text_editor::Content::with_text(&code); + } + Err(error) => error_dialog(error.to_string()), + } } Message::DropNewElement(name, point, _) => { return iced_drop::zones_on_point( @@ -129,14 +124,26 @@ impl App { Message::HandleNew(name, zones) => { let ids: Vec<Id> = zones.into_iter().map(|z| z.0).collect(); if ids.len() > 0 { - let action = Action::new(ids, &mut self.project.element_tree.clone(), None); - let result = name.handle_action(self.project.element_tree.as_mut(), action); - if let Ok(Some(ref element)) = result { - self.project.element_tree = Some(element.clone()); + let action = Action::new( + ids, + &mut self.project.element_tree.clone(), + None, + ); + let result = name.handle_action( + self.project.element_tree.as_mut(), + action, + ); + match result { + Ok(Some(ref element)) => { + self.project.element_tree = Some(element.clone()) + } + Err(error) => error_dialog(error.to_string()), + _ => {} } - } - return Task::done(Message::RefreshEditorContent); + self.is_dirty = true; + return Task::done(Message::RefreshEditorContent); + } } Message::MoveElement(element, point, _) => { return iced_drop::zones_on_point( @@ -155,10 +162,17 @@ impl App { &mut self.project.element_tree.clone(), Some(element.get_id()), ); - let _ = element.handle_action(self.project.element_tree.as_mut(), action); - } + let result = element.handle_action( + self.project.element_tree.as_mut(), + action, + ); + if let Err(error) = result { + error_dialog(error.to_string()); + } - return Task::done(Message::RefreshEditorContent); + self.is_dirty = true; + return Task::done(Message::RefreshEditorContent); + } } Message::PaneResized(pane_grid::ResizeEvent { split, ratio }) => { self.pane_state.resize(split, ratio); @@ -166,34 +180,62 @@ impl App { Message::PaneClicked(pane) => { self.focus = Some(pane); } - Message::PaneDragged(pane_grid::DragEvent::Dropped { pane, target }) => { + Message::PaneDragged(pane_grid::DragEvent::Dropped { + pane, + target, + }) => { self.pane_state.drop(pane, target); } Message::PaneDragged(_) => {} Message::NewFile => { if !self.is_loading { - self.project = Project::new(); - self.project_path = None; - self.editor_content = text_editor::Content::new(); + if !self.is_dirty { + self.project = Project::new(); + self.project_path = None; + self.editor_content = text_editor::Content::new(); + } else { + if let MessageDialogResult::Ok = unsaved_changes_dialog("You have unsaved changes. Do you wish to discard these and create a new project?") { + self.is_dirty = false; + self.project = Project::new(); + self.project_path = None; + self.editor_content = text_editor::Content::new(); + } + } } } Message::OpenFile => { if !self.is_loading { - self.is_loading = true; + if !self.is_dirty { + self.is_loading = true; - return Task::perform(Project::from_file(), Message::FileOpened); + return Task::perform( + Project::from_path(), + Message::FileOpened, + ); + } else { + if let MessageDialogResult::Ok = unsaved_changes_dialog("You have unsaved changes. Do you wish to discard these and open another project?") { + self.is_dirty = false; + self.is_loading = true; + return Task::perform(Project::from_path(), Message::FileOpened); + } + } } } Message::FileOpened(result) => { self.is_loading = false; self.is_dirty = false; - if let Ok((path, project)) = result { - self.project = project.clone(); - self.project_path = Some(path); - self.editor_content = text_editor::Content::with_text( - &project.app_code().unwrap_or_else(|err| err.to_string()), - ); + match result { + Ok((path, project)) => { + self.project = project.clone(); + self.project_path = Some(path); + self.editor_content = text_editor::Content::with_text( + &project + .app_code() + .unwrap_or_else(|err| err.to_string()), + ); + } + Err(error) => error_dialog(error.to_string()), } } Message::SaveFile => { @@ -221,9 +263,12 @@ impl App { Message::FileSaved(result) => { self.is_loading = false; - if let Ok(path) = result { - self.project_path = Some(path); - self.is_dirty = false; + match result { + Ok(path) => { + self.project_path = Some(path); + self.is_dirty = false; + } + Err(error) => error_dialog(error.to_string()), } } } @@ -232,47 +277,60 @@ impl App { } fn subscription(&self) -> iced::Subscription<Message> { - keyboard::on_key_press(|key, modifiers| match key.as_ref() { - keyboard::Key::Character("o") if modifiers.command() => Some(Message::OpenFile), - keyboard::Key::Character("s") if modifiers.command() => { - if modifiers.shift() { - Some(Message::SaveFileAs) - } else { - Some(Message::SaveFile) + keyboard::on_key_press(|key, modifiers| { + if modifiers.command() { + match key.as_ref() { + keyboard::Key::Character("o") => Some(Message::OpenFile), + keyboard::Key::Character("s") => { + Some(if modifiers.shift() { + Message::SaveFileAs + } else { + Message::SaveFile + }) + } + keyboard::Key::Character("n") => Some(Message::NewFile), + _ => None, } + } else { + None } - keyboard::Key::Character("n") if modifiers.command() => Some(Message::NewFile), - _ => None, }) } - fn view(&self) -> Element<Message> { - let header = row![button("Toggle Theme") - .on_press(Message::ToggleTheme) - .padding(5)] + fn view(&self) -> Element<'_, Message> { + let header = row![pick_list( + THEMES, + Some(self.theme.target().clone()), + |theme| { Message::ToggleTheme(theme.into()) } + )] .width(200); - let pane_grid = PaneGrid::new(&self.pane_state, |id, pane, _is_maximized| { - let is_focused = Some(id) == self.focus; - match pane { - Panes::Designer => match &self.designer_page { - DesignerPage::Designer => designer_view::view( - &self.project.element_tree, - self.project.get_theme(), - is_focused, - ), - DesignerPage::CodeView => { - code_view::view(&self.editor_content, self.dark_theme, is_focused) + let pane_grid = + PaneGrid::new(&self.pane_state, |id, pane, _is_maximized| { + let is_focused = Some(id) == self.focus; + match pane { + Panes::Designer => match &self.designer_page { + DesignerPage::DesignerView => designer_view::view( + &self.project.element_tree, + self.project.get_theme(), + is_focused, + ), + DesignerPage::CodeView => code_view::view( + &self.editor_content, + self.theme.value().clone(), + is_focused, + ), + }, + Panes::ElementList => { + element_list::view(self.element_list, is_focused) } - }, - Panes::ElementList => element_list::view(&self.element_list, is_focused), - } - }) - .width(Length::Fill) - .height(Length::Fill) - .spacing(10) - .on_resize(10, Message::PaneResized) - .on_click(Message::PaneClicked) - .on_drag(Message::PaneDragged); + } + }) + .width(Length::Fill) + .height(Length::Fill) + .spacing(10) + .on_resize(10, Message::PaneResized) + .on_click(Message::PaneClicked) + .on_drag(Message::PaneDragged); let content = Column::new() .push(header) @@ -281,6 +339,8 @@ impl App { .align_x(Alignment::Center) .width(Length::Fill); - container(content).height(Length::Fill).into() + Animation::new(&self.theme, container(content).height(Length::Fill)) + .on_update(Message::ToggleTheme) + .into() } } |
