diff options
author | Akshay <[email protected]> | 2021-03-17 12:22:40 +0000 |
---|---|---|
committer | Akshay <[email protected]> | 2021-03-17 12:22:40 +0000 |
commit | 7615546fb0157c3ec9d2f25ec9837ee0b6cb7e9a (patch) | |
tree | b197326f7812ec20e2207b0e444f2a50569c5b74 /src/app.rs | |
parent | 83732aed1a41a713cd8790fcebae90aabe78b789 (diff) |
feat: basic command mode, add text box primitives
Diffstat (limited to 'src/app.rs')
-rw-r--r-- | src/app.rs | 308 |
1 files changed, 211 insertions, 97 deletions
@@ -23,21 +23,29 @@ use sdl2::{ | |||
23 | Sdl, | 23 | Sdl, |
24 | }; | 24 | }; |
25 | 25 | ||
26 | #[derive(Debug, Copy, Clone, PartialEq)] | ||
27 | pub enum Mode { | ||
28 | Draw, | ||
29 | Command, | ||
30 | } | ||
31 | |||
26 | pub struct AppState<'ctx> { | 32 | pub 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(); |