// Stiletto // Copyright (C) 2020 Stiletto Authors // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . use std::path::PathBuf; use log::{info, warn}; use druid::commands; use druid::im::vector; use druid::widget::prelude::*; use druid::widget::{Align, Button, CrossAxisAlignment, Flex, List, SizedBox, WidgetExt}; use druid::{ AppDelegate, AppLauncher, Color, Command, Data, DelegateCtx, Env, FileDialogOptions, FileSpec, Handled, Lens, LocalizedString, Target, WidgetId, WindowDesc, }; use im::Vector; use stiletto::tool::{CanvasToolCtx, CanvasToolParams}; use stiletto::widget::{ build_simple_tool_widget, CanvasState, CanvasToolIconState, CanvasWidget, SCROLL, }; use stiletto::DocumentSnapshot; pub fn main() { let window = WindowDesc::new(build_ui) .window_size((1024.0, 1400.0)) .title(|data: &StilettoState, _env: &Env| { let doc_name = if let Some(path) = &data.current_file_path { String::from(path.to_string_lossy()) } else { String::from("New Document") }; format!("Stiletto - {}", doc_name) }); let default_pen_params = CanvasToolParams::Pen { thickness: 2.0, color: Color::rgb(0.0, 0.0, 0.0), }; let default_pen_params_2 = CanvasToolParams::Pen { thickness: 2.0, color: Color::rgb(255.0, 0.0, 0.0), }; let canvas_data = StilettoState { canvas: CanvasState::new(CanvasToolCtx::new(default_pen_params.clone())), tool_icons: vector![ CanvasToolIconState { tool_params: default_pen_params, selected: true, }, CanvasToolIconState { tool_params: default_pen_params_2, selected: false, }, CanvasToolIconState { tool_params: CanvasToolParams::Eraser, selected: false, }, ], current_tool: 0, current_file_path: None, }; AppLauncher::with_window(window) .use_simple_logger() .delegate(Delegate) .launch(canvas_data) .expect("launch failed"); } #[derive(Clone, Data, Lens)] struct StilettoState { canvas: CanvasState, tool_icons: Vector, current_tool: usize, #[data(same_fn = "PartialEq::eq")] current_file_path: Option, } fn build_ui() -> impl Widget { let canvas_id = WidgetId::next(); let history_buttons = Flex::row() .cross_axis_alignment(CrossAxisAlignment::Center) .with_child(Button::new("Undo").on_click( |_ctx: &mut EventCtx, data: &mut StilettoState, _env: &Env| data.canvas.perform_undo(), )) .with_child(Button::new("Redo").on_click( |_ctx: &mut EventCtx, data: &mut StilettoState, _env: &Env| data.canvas.perform_redo(), )); let stlt = FileSpec::new("Stiletto notebook", &["stlt"]); let save_dialog_options = FileDialogOptions::new() .allowed_types(vec![stlt]) .default_type(stlt); let open_dialog_options = save_dialog_options.clone(); let save_buttons = Flex::row() .cross_axis_alignment(CrossAxisAlignment::Center) .with_child(Button::new("Up").on_click( move |ctx: &mut EventCtx, _data: &mut StilettoState, _env: &Env| { ctx.submit_command(Command::new(SCROLL, -30.0, canvas_id)); }, )) .with_child(Button::new("Down").on_click( move |ctx: &mut EventCtx, _data: &mut StilettoState, _env: &Env| { ctx.submit_command(Command::new(SCROLL, 30.0, canvas_id)); }, )) .with_child(Button::new("Open").on_click(move |ctx, _, _| { ctx.submit_command(Command::new( druid::commands::SHOW_OPEN_PANEL, open_dialog_options.clone(), Target::Auto, )) })) .with_child(Button::new("Save").on_click(move |ctx, _, _| { ctx.submit_command(Command::new( druid::commands::SHOW_SAVE_PANEL, save_dialog_options.clone(), Target::Auto, )) })); let tool_buttons = Flex::row() .cross_axis_alignment(CrossAxisAlignment::Center) .with_flex_child( List::new(|| { build_simple_tool_widget(30.0, 30.0, 5.0) .padding((8.0, 0.0)) .on_click(|ctx, tool_state, _| { tool_state.selected = true; ctx.submit_command(stiletto::commands::UPDATE_TOOL); }) }) .horizontal() .lens(StilettoState::tool_icons), 1.0, ); let toolbar = Flex::row() .cross_axis_alignment(CrossAxisAlignment::Center) .with_spacer(30.0) .with_flex_child(Align::left(history_buttons), 1.0) .with_spacer(10.0) .with_flex_child(Align::left(tool_buttons), 2.0) .with_spacer(20.0) .with_flex_child(Align::right(save_buttons), 1.0) .with_spacer(30.0); Flex::column() .cross_axis_alignment(CrossAxisAlignment::Center) .must_fill_main_axis(true) .with_child(SizedBox::new(Align::left(toolbar)).height(50.0)) .with_flex_child( CanvasWidget::new() .lens(StilettoState::canvas) .with_id(canvas_id), 1.0, ) } struct Delegate; impl AppDelegate for Delegate { fn command( &mut self, _ctx: &mut DelegateCtx, _target: Target, cmd: &Command, data: &mut StilettoState, _env: &Env, ) -> Handled { use std::fs::File; if cmd.get(commands::SAVE_FILE).is_some() { //let path = data.current_file_path.as_ref().unwrap(); // TODO } if let Some(file_info) = cmd.get(commands::SAVE_FILE_AS) { let res_file = File::create(file_info.path()); if let Ok(f) = res_file { let write_res = serde_json::to_writer_pretty(f, &data.canvas.get_document_snapshot()); if write_res.is_err() { warn!("Error while saving: {:?}", write_res.err()); } else { data.current_file_path = Some(file_info.path().to_path_buf()); info!("Written to file: {}", file_info.path().display()); } } else { warn!("Cannot create file: {:?}", res_file.err()); } return Handled::Yes; } if let Some(file_info) = cmd.get(commands::OPEN_FILE) { if let Ok(f) = File::open(file_info.path()) { let res_snapshot: Result = serde_json::from_reader(f); if let Ok(document_snapshot) = res_snapshot { data.canvas.set_from_snapshot(document_snapshot); data.current_file_path = Some(file_info.path().to_path_buf()); info!("Loaded file {}", file_info.path().display()); } else { warn!( "Error while loading {}: {:?}", file_info.path().display(), res_snapshot.err() ); } } return Handled::Yes; } if cmd.get(stiletto::commands::UPDATE_TOOL).is_some() { let old_tool = data.current_tool; if let Some(new_tool) = data .tool_icons .iter() .enumerate() .position(|(pos, x)| pos != old_tool && x.selected) { info!("Changing active tool to: tool #{}", new_tool); data.tool_icons.get_mut(old_tool).unwrap().selected = false; data.current_tool = new_tool; data.canvas.set_tool_ctx(CanvasToolCtx::new( data.tool_icons.get(new_tool).unwrap().tool_params.clone(), )); } } Handled::No } }