use fltk::{
app, dialog,
enums::{CallbackTrigger, Color, Event, Font, FrameType, Shortcut},
menu,
prelude::*,
printer, text, window,
};
use std::path::PathBuf;
use std::{
error,
ops::{Deref, DerefMut},
path,
};
#[derive(Copy, Clone)]
pub enum Message {
Changed,
New,
Open,
Save,
SaveAs,
Print,
Quit,
Cut,
Copy,
Paste,
About,
}
pub fn center() -> (i32, i32) {
(
(app::screen_size().0 / 2.0) as i32,
(app::screen_size().1 / 2.0) as i32,
)
}
pub struct MyEditor {
editor: text::TextEditor,
}
impl MyEditor {
pub fn new(buf: text::TextBuffer) -> Self {
let mut editor = text::TextEditor::new(5, 35, 790, 560, "");
editor.set_buffer(Some(buf));
#[cfg(target_os = "macos")]
editor.resize(5, 5, 790, 590);
editor.set_scrollbar_size(15);
editor.set_text_font(Font::Courier);
editor.set_linenumber_width(32);
editor.set_linenumber_fgcolor(Color::from_u32(0x008b_8386));
editor.set_trigger(CallbackTrigger::Changed);
Self { editor }
}
}
impl Deref for MyEditor {
type Target = text::TextEditor;
fn deref(&self) -> &Self::Target {
&self.editor
}
}
impl DerefMut for MyEditor {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.editor
}
}
pub struct MyMenu {
menu: menu::SysMenuBar,
}
impl MyMenu {
pub fn new(s: &app::Sender<Message>) -> Self {
let mut menu = menu::SysMenuBar::default().with_size(800, 35);
menu.set_frame(FrameType::FlatBox);
menu.add_emit(
"&File/New...\t",
Shortcut::Ctrl | 'n',
menu::MenuFlag::Normal,
*s,
Message::New,
);
menu.add_emit(
"&File/Open...\t",
Shortcut::Ctrl | 'o',
menu::MenuFlag::Normal,
*s,
Message::Open,
);
menu.add_emit(
"&File/Save\t",
Shortcut::Ctrl | 's',
menu::MenuFlag::Normal,
*s,
Message::Save,
);
menu.add_emit(
"&File/Save as...\t",
Shortcut::Ctrl | 'w',
menu::MenuFlag::Normal,
*s,
Message::SaveAs,
);
menu.add_emit(
"&File/Print...\t",
Shortcut::Ctrl | 'p',
menu::MenuFlag::MenuDivider,
*s,
Message::Print,
);
menu.add_emit(
"&File/Quit\t",
Shortcut::Ctrl | 'q',
menu::MenuFlag::Normal,
*s,
Message::Quit,
);
menu.add_emit(
"&Edit/Cut\t",
Shortcut::Ctrl | 'x',
menu::MenuFlag::Normal,
*s,
Message::Cut,
);
menu.add_emit(
"&Edit/Copy\t",
Shortcut::Ctrl | 'c',
menu::MenuFlag::Normal,
*s,
Message::Copy,
);
menu.add_emit(
"&Edit/Paste\t",
Shortcut::Ctrl | 'v',
menu::MenuFlag::Normal,
*s,
Message::Paste,
);
menu.add_emit(
"&Help/About\t",
Shortcut::None,
menu::MenuFlag::Normal,
*s,
Message::About,
);
Self { menu }
}
}
pub struct MyApp {
app: app::App,
modified: bool,
filename: Option<PathBuf>,
r: app::Receiver<Message>,
main_win: window::Window,
menu: MyMenu,
buf: text::TextBuffer,
editor: MyEditor,
printable: text::TextDisplay,
}
impl MyApp {
pub fn new(args: Vec<String>) -> Self {
let app = app::App::default().with_scheme(app::Scheme::Gtk);
app::background(211, 211, 211);
let (s, r) = app::channel::<Message>();
let mut buf = text::TextBuffer::default();
buf.set_tab_distance(4);
let mut main_win = window::Window::default()
.with_size(800, 600)
.center_screen()
.with_label("RustyEd");
let menu = MyMenu::new(&s);
let modified = false;
menu.menu.find_item("&File/Save\t").unwrap().deactivate();
let mut editor = MyEditor::new(buf.clone());
editor.emit(s, Message::Changed);
main_win.make_resizable(true);
main_win.resizable(&*editor);
main_win.end();
main_win.show();
main_win.set_callback(move |_| {
if app::event() == Event::Close {
s.send(Message::Quit);
}
});
let filename = if args.len() > 1 {
let file = path::Path::new(&args[1]);
assert!(
file.exists() && file.is_file(),
"An error occurred while opening the file!"
);
match buf.load_file(&args[1]) {
Ok(_) => Some(PathBuf::from(args[1].clone())),
Err(e) => {
dialog::alert(
center().0 - 200,
center().1 - 100,
&format!("An issue occured while loading the file: {e}"),
);
None
}
}
} else {
None
};
editor.handle({
let mut dnd = false;
let mut released = false;
let buf = buf.clone();
move |_, ev| match ev {
Event::DndEnter => {
dnd = true;
true
}
Event::DndDrag => true,
Event::DndRelease => {
released = true;
true
}
Event::Paste => {
if dnd && released {
let path = app::event_text();
let path = path.trim();
let path = path.replace("file://", "");
let path = std::path::PathBuf::from(&path);
if path.exists() {
app::add_timeout3(0.0, {
let mut buf = buf.clone();
move |_| match buf.load_file(&path) {
Ok(_) => (),
Err(e) => dialog::alert(
center().0 - 200,
center().1 - 100,
&format!("An issue occured while loading the file: {e}"),
),
}
});
}
dnd = false;
released = false;
true
} else {
false
}
}
Event::DndLeave => {
dnd = false;
released = false;
true
}
_ => false,
}
});
let mut printable = text::TextDisplay::default();
printable.set_frame(FrameType::NoBox);
printable.set_scrollbar_size(0);
printable.set_buffer(Some(buf.clone()));
Self {
app,
modified,
filename,
r,
main_win,
menu,
buf,
editor,
printable,
}
}
pub fn save_file(&mut self) -> Result<bool, Box<dyn error::Error>> {
match &self.filename {
Some(f) => {
self.buf.save_file(f)?;
self.modified = false;
self.menu
.menu
.find_item("&File/Save\t")
.unwrap()
.deactivate();
self.menu
.menu
.find_item("&File/Quit\t")
.unwrap()
.set_label_color(Color::Black);
let name = match &self.filename {
Some(f) => f.to_string_lossy().to_string(),
None => "(Untitled)".to_string(),
};
self.main_win.set_label(&format!("{name} - RustyEd"));
Ok(true)
}
None => self.save_file_as(),
}
}
pub fn save_file_as(&mut self) -> Result<bool, Box<dyn error::Error>> {
let mut dlg = dialog::FileDialog::new(dialog::FileDialogType::BrowseSaveFile);
dlg.set_option(dialog::FileDialogOptions::SaveAsConfirm);
dlg.show();
if dlg.filename().to_string_lossy().to_string().is_empty() {
dialog::alert(center().0 - 200, center().1 - 100, "Please specify a file!");
return Ok(false);
}
self.buf.save_file(&dlg.filename())?;
self.modified = false;
self.menu
.menu
.find_item("&File/Save\t")
.unwrap()
.deactivate();
self.menu
.menu
.find_item("&File/Quit\t")
.unwrap()
.set_label_color(Color::Black);
self.filename = Some(dlg.filename());
self.main_win
.set_label(&format!("{:?} - RustyEd", self.filename.as_ref().unwrap()));
Ok(true)
}
pub fn launch(&mut self) {
while self.app.wait() {
use Message::*;
if let Some(msg) = self.r.recv() {
match msg {
Changed => {
if !self.modified {
self.modified = true;
self.menu.menu.find_item("&File/Save\t").unwrap().activate();
self.menu.menu.find_item("&File/Quit\t").unwrap().set_label_color(Color::Red);
let name = match &self.filename {
Some(f) => f.to_string_lossy().to_string(),
None => "(Untitled)".to_string(),
};
self.main_win.set_label(&format!("* {name} - RustyEd"));
}
}
New => {
if self.buf.text() != "" {
let clear = if let Some(x) = dialog::choice2(center().0 - 200, center().1 - 100, "File unsaved, Do you wish to continue?", "Yes", "No!", "") {
x == 0
} else {
false
};
if clear {
self.buf.set_text("");
}
}
},
Open => {
let mut dlg = dialog::FileDialog::new(dialog::FileDialogType::BrowseFile);
dlg.set_option(dialog::FileDialogOptions::NoOptions);
dlg.set_filter("*.{txt,rs,toml}");
dlg.show();
let filename = dlg.filename();
if !filename.to_string_lossy().to_string().is_empty() {
if filename.exists() {
match self.buf.load_file(&filename) {
Ok(_) => self.filename = Some(filename),
Err(e) => dialog::alert(center().0 - 200, center().1 - 100, &format!("An issue occured while loading the file: {e}")),
}
} else {
dialog::alert(center().0 - 200, center().1 - 100, "File does not exist!")
}
}
},
Save => { self.save_file().unwrap(); },
SaveAs => { self.save_file_as().unwrap(); },
Print => {
let mut printer = printer::Printer::default();
if printer.begin_job(0).is_ok() {
let (w, h) = printer.printable_rect();
self.printable.set_size(w - 40, h - 40);
let line_count = self.printable.count_lines(0, self.printable.buffer().unwrap().length(), true) / 45;
for i in 0..=line_count {
self.printable.scroll(45 * i, 0);
printer.begin_page().ok();
printer.print_widget(&self.printable, 20, 20);
printer.end_page().ok();
}
printer.end_job();
}
},
Quit => {
if self.modified {
match dialog::choice2(center().0 - 200, center().1 - 100,
"Would you like to save your work?", "Yes", "No", "") {
Some(0) => {
if self.save_file().unwrap() {
self.app.quit();
}
},
Some(1) => { self.app.quit() },
Some(_) | None => (),
}
} else {
self.app.quit();
}
},
Cut => self.editor.cut(),
Copy => self.editor.copy(),
Paste => self.editor.paste(),
About => dialog::message(center().0 - 300, center().1 - 100, "This is an example application written in Rust and using the FLTK Gui library."),
}
}
}
}
}
fn main() {
let args: Vec<_> = std::env::args().collect();
let mut app = MyApp::new(args);
app.launch();
}