246 lines
8.7 KiB
Rust
246 lines
8.7 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, 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<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 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<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).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<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
|
|
}
|
|
}
|