stiletto/src/main.rs

260 lines
9.3 KiB
Rust

// 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 <https://www.gnu.org/licenses/>.
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, 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<CanvasToolIconState>,
current_tool: usize,
#[data(same_fn = "PartialEq::eq")]
current_file_path: Option<PathBuf>,
}
fn build_ui() -> impl Widget<StilettoState> {
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 save_dialog_options_clone = save_dialog_options.clone();
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("Save").on_click(move |ctx, data: &mut StilettoState, _| {
if data.current_file_path.is_some() {
ctx.submit_command(Command::new(commands::SAVE_FILE, (), Target::Auto));
} else {
ctx.submit_command(Command::new(
druid::commands::SHOW_SAVE_PANEL,
save_dialog_options_clone.clone(),
Target::Auto,
))
}
}))
.with_child(Button::new("Save As").on_click(move |ctx, _, _| {
ctx.submit_command(Command::new(
druid::commands::SHOW_SAVE_PANEL,
save_dialog_options.clone(),
Target::Auto,
))
}))
.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,
))
}));
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<StilettoState> 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_AS).is_some() || cmd.get(commands::SAVE_FILE).is_some() {
let mut path_buf = cmd.get(commands::SAVE_FILE_AS)
.map(|file_info| file_info.path().to_path_buf())
.unwrap_or(data.current_file_path.as_ref().cloned().unwrap());
path_buf.set_extension("stlt");
let res_file = File::create(&path_buf);
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 {
info!("Written to file: {}", &path_buf.display());
data.current_file_path = Some(path_buf);
}
} 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<DocumentSnapshot, serde_json::Error> =
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
}
}