aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAkshay <[email protected]>2021-03-17 12:22:40 +0000
committerAkshay <[email protected]>2021-03-17 12:22:40 +0000
commit7615546fb0157c3ec9d2f25ec9837ee0b6cb7e9a (patch)
treeb197326f7812ec20e2207b0e444f2a50569c5b74
parent83732aed1a41a713cd8790fcebae90aabe78b789 (diff)
feat: basic command mode, add text box primitives
-rw-r--r--src/app.rs308
-rw-r--r--src/command.rs134
-rw-r--r--src/main.rs36
-rw-r--r--src/utils.rs17
4 files changed, 396 insertions, 99 deletions
diff --git a/src/app.rs b/src/app.rs
index 5b27422..0eac084 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -23,21 +23,29 @@ use sdl2::{
23 Sdl, 23 Sdl,
24}; 24};
25 25
26#[derive(Debug, Copy, Clone, PartialEq)]
27pub enum Mode {
28 Draw,
29 Command,
30}
31
26pub struct AppState<'ctx> { 32pub struct AppState<'ctx> {
27 active_color: bool, 33 active_color: bool,
28 brush_size: u8, 34 brush_size: u8,
29 dither_level: u8,
30 canvas: Canvas<Window>, 35 canvas: Canvas<Window>,
31 context: &'ctx Sdl, 36 context: &'ctx Sdl,
32 ttf_context: &'ctx Sdl2TtfContext,
33 mouse: (i32, i32),
34 current_operation: Operation, 37 current_operation: Operation,
38 dither_level: u8,
35 grid: Grid, 39 grid: Grid,
36 last_point: Option<Point>, 40 last_point: Option<MapPoint>,
41 mode: Mode,
42 mouse: (i32, i32),
37 pixmap: Pixmap<bool>, 43 pixmap: Pixmap<bool>,
38 start: Point, 44 start: Point,
39 symmetry: Symmetry, 45 symmetry: Symmetry,
46 ttf_context: &'ctx Sdl2TtfContext,
40 undo_stack: UndoStack<Operation>, 47 undo_stack: UndoStack<Operation>,
48 command_box: CommandBox,
41 zoom: u8, 49 zoom: u8,
42} 50}
43 51
@@ -153,15 +161,15 @@ impl<'ctx> AppState<'ctx> {
153 161
154 fn paint_line<P: Into<Point>>( 162 fn paint_line<P: Into<Point>>(
155 &mut self, 163 &mut self,
156 start: P, 164 start: MapPoint,
157 end: P, 165 end: P,
158 val: bool, 166 val: bool,
159 ) -> Result<Vec<ModifyRecord>, ()> { 167 ) -> Result<Vec<ModifyRecord>, ()> {
160 let start = self.idx_at_coord(start).ok_or(())?; 168 let MapPoint { x, y } = start;
161 let end = self.idx_at_coord(end).ok_or(())?; 169 let end = self.idx_at_coord(end).ok_or(())?;
162 let dither_level = self.dither_level; 170 let dither_level = self.dither_level;
163 171
164 let line = self.pixmap.get_line(start, end); 172 let line = self.pixmap.get_line((x, y), end);
165 let sym_line = self.symmetry.apply(&line); 173 let sym_line = self.symmetry.apply(&line);
166 174
167 let mut line_modify_record = vec![]; 175 let mut line_modify_record = vec![];
@@ -239,6 +247,21 @@ impl<'ctx> AppState<'ctx> {
239 } 247 }
240 } 248 }
241 249
250 fn eval_command(&mut self) {
251 match self.command_box.text.as_str() {
252 "(save)" => {
253 let image = self.export();
254 let encoded = image.encode().unwrap();
255 let mut buffer = File::create("test.obi").unwrap();
256 eprintln!("writing to file");
257 buffer.write_all(&encoded[..]).unwrap();
258 }
259 _ => {}
260 }
261 self.command_box.clear();
262 self.mode = Mode::Draw;
263 }
264
242 fn zoom_out(&mut self, p: (i32, i32)) { 265 fn zoom_out(&mut self, p: (i32, i32)) {
243 if self.zoom > 1 { 266 if self.zoom > 1 {
244 // attempt to center around cursor 267 // attempt to center around cursor
@@ -309,6 +332,27 @@ impl<'ctx> AppState<'ctx> {
309 ); 332 );
310 } 333 }
311 334
335 fn draw_command_box(&mut self) {
336 if self.command_box.is_empty() {
337 self.mode = Mode::Draw;
338 return;
339 }
340 let (winsize_x, winsize_y) = self.canvas.window().size();
341 let cmd_height: u32 = 20;
342 let cmd_width = winsize_x;
343 self.canvas.set_draw_color(WHITE);
344 self.canvas
345 .fill_rect(rect!(0, winsize_y - cmd_height, cmd_width, cmd_height))
346 .unwrap();
347 draw_text(
348 &mut self.canvas,
349 self.ttf_context,
350 &self.command_box.text[..],
351 BLACK,
352 (0, winsize_y - cmd_height),
353 );
354 }
355
312 fn draw_mouse(&mut self) { 356 fn draw_mouse(&mut self) {
313 let brush_size = self.brush_size; 357 let brush_size = self.brush_size;
314 let cs = self.zoom as u32; 358 let cs = self.zoom as u32;
@@ -368,7 +412,11 @@ impl<'ctx> AppState<'ctx> {
368 .draw_line((line_coord, 0), (line_coord, winsize_y as i32)) 412 .draw_line((line_coord, 0), (line_coord, winsize_y as i32))
369 .unwrap(); 413 .unwrap();
370 } 414 }
371 self.draw_statusline(); 415 if self.mode == Mode::Draw {
416 self.draw_statusline();
417 } else {
418 self.draw_command_box();
419 }
372 self.draw_mouse(); 420 self.draw_mouse();
373 } 421 }
374 422
@@ -446,109 +494,175 @@ impl<'ctx> AppState<'ctx> {
446 let mouse = event_pump.mouse_state(); 494 let mouse = event_pump.mouse_state();
447 self.mouse = (mouse.x(), mouse.y()); 495 self.mouse = (mouse.x(), mouse.y());
448 for event in event_pump.poll_iter() { 496 for event in event_pump.poll_iter() {
449 match event { 497 if let Event::KeyDown {
450 Event::KeyDown { 498 keycode: Some(Keycode::Num9),
451 keycode: Some(k), .. 499 keymod,
452 } => { 500 ..
453 match k { 501 } = event
454 // pan 502 {
455 Keycode::W => self.pan((0, 10)), 503 if keymod == Mod::LSHIFTMOD || keymod == Mod::RSHIFTMOD {
456 Keycode::A => self.pan((10, 0)), 504 self.mode = Mode::Command;
457 Keycode::S => self.pan((0, -10)), 505 }
458 Keycode::D => self.pan((-10, 0)), 506 }
459 // zoom 507 match self.mode {
460 Keycode::C => { 508 Mode::Draw => {
461 let cursor = (mouse.x(), mouse.y()); 509 match event {
462 self.zoom_in(cursor); 510 Event::KeyDown {
463 } 511 keycode: Some(k), ..
464 Keycode::Z => { 512 } => {
465 let cursor = (mouse.x(), mouse.y()); 513 match k {
466 self.zoom_out(cursor); 514 // pan
467 } 515 Keycode::W => self.pan((0, 10)),
468 // brush ops 516 Keycode::A => self.pan((10, 0)),
469 Keycode::Q => self.decrease_brush_size(), 517 Keycode::S => self.pan((0, -10)),
470 Keycode::E => self.increase_brush_size(), 518 Keycode::D => self.pan((-10, 0)),
471 Keycode::Num1 => self.reduce_intensity(), 519 // zoom
472 Keycode::Num3 => self.increase_intensity(), 520 Keycode::C => {
473 // flip color 521 let cursor = (mouse.x(), mouse.y());
474 Keycode::X => self.change_active_color(), 522 self.zoom_in(cursor);
475 // toggle grid
476 Keycode::Tab => self.toggle_grid(),
477 // line drawing
478 Keycode::F => {
479 let end = (mouse.x(), mouse.y()).into();
480 if let Some(start) = self.last_point {
481 if let Ok(o) = self.paint_line(start, end, self.active_color) {
482 self.commit_operation();
483 self.current_operation =
484 o.into_iter().filter(|v| !v.old_val == v.val).collect();
485 self.commit_operation();
486 self.last_point = Some(end);
487 } 523 }
524 Keycode::Z => {
525 let cursor = (mouse.x(), mouse.y());
526 self.zoom_out(cursor);
527 }
528 // brush ops
529 Keycode::Q => self.decrease_brush_size(),
530 Keycode::E => self.increase_brush_size(),
531 Keycode::Num1 => self.reduce_intensity(),
532 Keycode::Num3 => self.increase_intensity(),
533 // flip color
534 Keycode::X => self.change_active_color(),
535 // toggle grid
536 Keycode::Tab => self.toggle_grid(),
537 // line drawing
538 Keycode::F => {
539 let end: Point = (mouse.x(), mouse.y()).into();
540 if let Some(start) = self.last_point {
541 if let Ok(o) =
542 self.paint_line(start, end, self.active_color)
543 {
544 self.commit_operation();
545 self.current_operation = o
546 .into_iter()
547 .filter(|v| !v.old_val == v.val)
548 .collect();
549 self.commit_operation();
550 self.last_point =
551 Some(self.idx_at_coord(end).unwrap().into());
552 }
553 }
554 }
555 Keycode::V => self.cycle_symmetry(),
556 // exit
557 Keycode::Escape => break 'running,
558 // undo & redo
559 Keycode::U => {
560 if let Some(op) = self.undo_stack.undo() {
561 self.apply_operation(op, OpKind::Undo);
562 }
563 }
564 // export to file
565 Keycode::N => {
566 let image = self.export();
567 let encoded = image.encode().unwrap();
568 let mut buffer = File::create("test.obi").unwrap();
569 eprintln!("writing to file");
570 buffer.write_all(&encoded[..]).unwrap();
571 }
572 Keycode::R => {
573 if let Some(op) = self.undo_stack.redo() {
574 self.apply_operation(op, OpKind::Redo);
575 }
576 }
577 _ => (),
488 } 578 }
489 } 579 }
490 Keycode::V => self.cycle_symmetry(), 580 // start of operation
491 // exit 581 Event::MouseButtonDown {
492 Keycode::Escape => break 'running, 582 x, y, mouse_btn, ..
493 // undo & redo 583 } => {
494 Keycode::U => { 584 let pt = (x, y);
495 if let Some(op) = self.undo_stack.undo() { 585 self.last_point = self.idx_at_coord(pt).map(MapPoint::from);
496 self.apply_operation(op, OpKind::Undo); 586 let val = match mouse_btn {
587 MouseButton::Right => !self.active_color,
588 _ => self.active_color,
589 };
590 if let Ok(o) = self.paint_point(pt, val) {
591 self.current_operation.extend(o);
497 } 592 }
498 } 593 }
499 Keycode::R => { 594 // click and drag
500 if let Some(op) = self.undo_stack.redo() { 595 Event::MouseMotion {
501 self.apply_operation(op, OpKind::Redo); 596 x, y, mousestate, ..
597 } => {
598 if mousestate.is_mouse_button_pressed(MouseButton::Left) {
599 let pt = (x, y);
600 let val = self.active_color;
601 if let Ok(o) = self.paint_point(pt, val) {
602 self.current_operation.extend(o);
603 }
604 } else if mousestate.is_mouse_button_pressed(MouseButton::Right) {
605 let pt = (x, y);
606 let val = !self.active_color;
607 if let Ok(o) = self.paint_point(pt, val) {
608 self.current_operation.extend(o);
609 }
502 } 610 }
503 } 611 }
504 _ => (), 612 // end of operation
613 Event::MouseButtonUp { .. } => {
614 let op = self
615 .current_operation
616 .drain(..)
617 .filter(|v| !v.old_val == v.val)
618 .collect::<Vec<_>>();
619 self.undo_stack.push(op);
620 }
621 Event::Quit { .. } => {
622 break 'running;
623 }
624 _ => {}
505 } 625 }
506 } 626 }
507 // start of operation 627 Mode::Command => {
508 Event::MouseButtonDown { 628 if let Event::KeyDown {
509 x, y, mouse_btn, .. 629 keycode, keymod, ..
510 } => { 630 } = event
511 let pt = (x, y); 631 {
512 self.last_point = Some(pt.into()); 632 let video = self.context.video().unwrap();
513 let val = match mouse_btn { 633 let clipboard = video.clipboard();
514 MouseButton::Right => !self.active_color, 634 if is_copy_event(keycode, keymod) {
515 _ => self.active_color, 635 clipboard.set_clipboard_text(&self.command_box.text);
516 }; 636 } else if is_paste_event(keycode, keymod)
517 if let Ok(o) = self.paint_point(pt, val) { 637 && clipboard.has_clipboard_text()
518 self.current_operation.extend(o); 638 {
639 self.command_box.text = clipboard.clipboard_text().unwrap();
640 }
519 } 641 }
520 } 642 if let Event::KeyDown {
521 // click and drag 643 keycode: Some(k), ..
522 Event::MouseMotion { 644 } = event
523 x, y, mousestate, .. 645 {
524 } => { 646 match k {
525 if mousestate.is_mouse_button_pressed(MouseButton::Left) { 647 Keycode::Backspace => self.command_box.backspace(),
526 let pt = (x, y); 648 Keycode::Delete => self.command_box.delete(),
527 let val = self.active_color; 649 Keycode::Left => self.command_box.backward(),
528 if let Ok(o) = self.paint_point(pt, val) { 650 Keycode::Right => self.command_box.forward(),
529 self.current_operation.extend(o); 651 Keycode::Return => self.eval_command(),
652 Keycode::Escape => {
653 self.command_box.clear();
654 self.mode = Mode::Draw;
655 }
656 _ => (),
530 } 657 }
531 } else if mousestate.is_mouse_button_pressed(MouseButton::Right) { 658 }
532 let pt = (x, y); 659 match event {
533 let val = !self.active_color; 660 Event::TextInput { text, .. } => {
534 if let Ok(o) = self.paint_point(pt, val) { 661 self.command_box.push_str(&text[..]);
535 self.current_operation.extend(o);
536 } 662 }
663 _ => (),
537 } 664 }
538 } 665 }
539 // end of operation
540 Event::MouseButtonUp { .. } => {
541 let op = self
542 .current_operation
543 .drain(..)
544 .filter(|v| !v.old_val == v.val)
545 .collect::<Vec<_>>();
546 self.undo_stack.push(op);
547 }
548 Event::Quit { .. } => {
549 break 'running;
550 }
551 _ => {}
552 } 666 }
553 } 667 }
554 self.redraw(); 668 self.redraw();
diff --git a/src/command.rs b/src/command.rs
new file mode 100644
index 0000000..3beb700
--- /dev/null
+++ b/src/command.rs
@@ -0,0 +1,134 @@
1#[derive(Debug)]
2pub struct CommandBox {
3 pub enabled: bool,
4 pub history: Vec<String>,
5 pub text: String,
6 pub cursor: usize,
7}
8
9// cursor value of 0 is behind all text
10// cursor value of n is after n characters (insert after index n - 1)
11// cursor value of text.len() is after all text
12
13impl CommandBox {
14 pub fn new() -> Self {
15 CommandBox {
16 enabled: false,
17 history: vec![],
18 text: String::new(),
19 cursor: 0,
20 }
21 }
22
23 pub fn forward(&mut self) {
24 if self.cursor < self.text.len() {
25 self.cursor += 1;
26 }
27 }
28
29 pub fn backward(&mut self) {
30 self.cursor = self.cursor.saturating_sub(1);
31 }
32
33 pub fn backspace(&mut self) {
34 if self.cursor != 0 {
35 self.text.remove(self.cursor - 1);
36 self.backward();
37 }
38 }
39
40 pub fn delete(&mut self) {
41 if self.cursor < self.text.len() {
42 self.text.remove(self.cursor);
43 }
44 }
45
46 pub fn push_str(&mut self, v: &str) {
47 self.text.push_str(v);
48 self.cursor += v.len();
49 }
50
51 pub fn is_empty(&self) -> bool {
52 self.text.is_empty()
53 }
54
55 pub fn clear(&mut self) {
56 self.text.clear();
57 self.cursor = 0;
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 fn setup_with(text: &str) -> CommandBox {
66 let mut cmd = CommandBox::new();
67 cmd.push_str(text);
68 cmd
69 }
70
71 #[test]
72 fn entering_text() {
73 let cmd = setup_with("save as file.png");
74 assert_eq!(&cmd.text, "save as file.png");
75 assert_eq!(cmd.cursor, 16)
76 }
77
78 #[test]
79 fn backspacing_from_end() {
80 let mut cmd = setup_with("save");
81 cmd.backspace();
82 assert_eq!(&cmd.text, "sav");
83 assert_eq!(cmd.cursor, 3);
84 }
85
86 #[test]
87 fn backspacing_from_middle() {
88 let mut cmd = setup_with("save");
89 cmd.backward();
90 cmd.backspace();
91 assert_eq!(&cmd.text, "sae");
92 assert_eq!(cmd.cursor, 2);
93 }
94
95 #[test]
96 fn delete() {
97 let mut cmd = setup_with("save");
98 cmd.backward();
99 cmd.delete();
100 assert_eq!(&cmd.text, "sav");
101 assert_eq!(cmd.cursor, 3);
102 }
103
104 #[test]
105 fn delete_end() {
106 let mut cmd = setup_with("save");
107 cmd.delete();
108 assert_eq!(&cmd.text, "save");
109 }
110
111 #[test]
112 fn delete_all() {
113 let mut cmd = setup_with("save");
114 for _ in 0..4 {
115 cmd.backward();
116 }
117 for _ in 0..4 {
118 cmd.delete();
119 }
120 assert_eq!(&cmd.text, "");
121 assert_eq!(cmd.cursor, 0);
122 }
123
124 #[test]
125 fn seeking() {
126 let mut cmd = setup_with("save");
127 for _ in 0..4 {
128 cmd.backward();
129 }
130 assert_eq!(cmd.cursor, 0);
131 cmd.forward();
132 assert_eq!(cmd.cursor, 1);
133 }
134}
diff --git a/src/main.rs b/src/main.rs
index 14f4cf5..ebdf793 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,6 @@
1mod app; 1mod app;
2mod bitmap; 2mod bitmap;
3mod command;
3mod consts; 4mod consts;
4mod dither; 5mod dither;
5mod symmetry; 6mod symmetry;
@@ -8,8 +9,41 @@ mod utils;
8 9
9use app::AppState; 10use app::AppState;
10 11
12use std::{
13 env,
14 fs::OpenOptions,
15 io::{Cursor, Read},
16};
17
18use obi::Image;
19
11pub fn main() { 20pub fn main() {
12 let sdl_context = sdl2::init().unwrap(); 21 let sdl_context = sdl2::init().unwrap();
13 let ttf_context = sdl2::ttf::init().unwrap(); 22 let ttf_context = sdl2::ttf::init().unwrap();
14 AppState::init(200, 200, &sdl_context, &ttf_context).run(); 23 let args: Vec<_> = env::args().collect();
24 if args.len() < 2 {
25 AppState::init(200, 200, &sdl_context, &ttf_context, None).run();
26 return;
27 } else {
28 let path = args.get(1).unwrap();
29 let image_src = OpenOptions::new()
30 .read(true)
31 .write(false)
32 .create(false)
33 .open(path);
34 if let Ok(mut image) = image_src {
35 let mut buf = Vec::new();
36 image.read_to_end(&mut buf).unwrap();
37 let decoded = Image::decode(&mut (Cursor::new(buf))).unwrap();
38 let (width, height) = (decoded.width(), decoded.height());
39 AppState::init(
40 width,
41 height,
42 &sdl_context,
43 &ttf_context,
44 Some(decoded.data),
45 )
46 .run();
47 }
48 }
15} 49}
diff --git a/src/utils.rs b/src/utils.rs
index a1b3624..71a9eea 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -1,5 +1,11 @@
1use crate::consts::FONT_PATH; 1use crate::consts::FONT_PATH;
2use sdl2::{pixels::Color, render::Canvas, ttf::Sdl2TtfContext, video::Window}; 2use sdl2::{
3 keyboard::{Keycode, Mod},
4 pixels::Color,
5 render::Canvas,
6 ttf::Sdl2TtfContext,
7 video::Window,
8};
3 9
4#[macro_export] 10#[macro_export]
5macro_rules! rect( 11macro_rules! rect(
@@ -27,4 +33,13 @@ pub fn draw_text<S: AsRef<str>>(
27 let (width, height) = font.size_of_latin1(text.as_bytes()).unwrap(); 33 let (width, height) = font.size_of_latin1(text.as_bytes()).unwrap();
28 let area = rect!(x, y, width, height); 34 let area = rect!(x, y, width, height);
29 canvas.copy(&texture, None, area).unwrap(); 35 canvas.copy(&texture, None, area).unwrap();
36 width
37}
38
39pub fn is_copy_event(keycode: Option<Keycode>, keymod: Mod) -> bool {
40 keycode == Some(Keycode::C) && (keymod == Mod::LCTRLMOD || keymod == Mod::RCTRLMOD)
41}
42
43pub fn is_paste_event(keycode: Option<Keycode>, keymod: Mod) -> bool {
44 keycode == Some(Keycode::V) && (keymod == Mod::LCTRLMOD || keymod == Mod::RCTRLMOD)
30} 45}