Solution

1. src
2. ├── main.rs
3. ├── widgets
4. │   ├── button.rs
5. │   ├── label.rs
6. │   └── window.rs
7. └── widgets.rs
1. // ---- src/widgets.rs ----
2. mod button;
3. mod label;
4. mod window;

6. pub trait Widget {
7. /// Natural width of `self`.
8. fn width(&self) -> usize;

10. /// Draw the widget into a buffer.
11. fn draw_into(&self, buffer: &mut dyn std::fmt::Write);

13. /// Draw the widget on standard output.
14. fn draw(&self) {
15. let mut buffer = String::new();
16. self.draw_into(&mut buffer);
17. println!("{buffer}");
18. }
19. }

21. pub use button::Button;
22. pub use label::Label;
23. pub use window::Window;
1. // ---- src/widgets/label.rs ----
2. use super::Widget;

4. pub struct Label {
5. label: String,
6. }

8. impl Label {
9. pub fn new(label: &str) -> Label {
10. Label { label: label.to_owned() }
11. }
12. }

14. impl Widget for Label {
15. fn width(&self) -> usize {
16. // ANCHOR_END: Label-width
17. self.label.lines().map(|line| line.chars().count()).max().unwrap_or(0)
18. }

20. // ANCHOR: Label-draw_into
21. fn draw_into(&self, buffer: &mut dyn std::fmt::Write) {
22. // ANCHOR_END: Label-draw_into
23. writeln!(buffer, "{}", &self.label).unwrap();
24. }
25. }
1. // ---- src/widgets/button.rs ----
2. use super::{Label, Widget};

4. pub struct Button {
5. label: Label,
6. }

8. impl Button {
9. pub fn new(label: &str) -> Button {
10. Button { label: Label::new(label) }
11. }
12. }

14. impl Widget for Button {
15. fn width(&self) -> usize {
16. // ANCHOR_END: Button-width
17. self.label.width() + 8 // add a bit of padding
18. }

20. // ANCHOR: Button-draw_into
21. fn draw_into(&self, buffer: &mut dyn std::fmt::Write) {
22. // ANCHOR_END: Button-draw_into
23. let width = self.width();
24. let mut label = String::new();
25. self.label.draw_into(&mut label);

27. writeln!(buffer, "+{:-<width$}+", "").unwrap();
28. for line in label.lines() {
29. writeln!(buffer, "|{:^width$}|", &line).unwrap();
30. }
31. writeln!(buffer, "+{:-<width$}+", "").unwrap();
32. }
33. }
1. // ---- src/widgets/window.rs ----
2. use super::Widget;

4. pub struct Window {
5. title: String,
6. widgets: Vec<Box<dyn Widget>>,
7. }

9. impl Window {
10. pub fn new(title: &str) -> Window {
11. Window { title: title.to_owned(), widgets: Vec::new() }
12. }

14. pub fn add_widget(&mut self, widget: Box<dyn Widget>) {
15. self.widgets.push(widget);
16. }

18. fn inner_width(&self) -> usize {
19. std::cmp::max(
20. self.title.chars().count(),
21. self.widgets.iter().map(|w| w.width()).max().unwrap_or(0),
22. )
23. }
24. }

26. impl Widget for Window {
27. fn width(&self) -> usize {
28. // ANCHOR_END: Window-width
29. // Add 4 paddings for borders
30. self.inner_width() + 4
31. }

33. // ANCHOR: Window-draw_into
34. fn draw_into(&self, buffer: &mut dyn std::fmt::Write) {
35. // ANCHOR_END: Window-draw_into
36. let mut inner = String::new();
37. for widget in &self.widgets {
38. widget.draw_into(&mut inner);
39. }

41. let inner_width = self.inner_width();

43. // TODO: after learning about error handling, you can change
44. // draw_into to return Result<(), std::fmt::Error>. Then use
45. // the ?-operator here instead of .unwrap().
46. writeln!(buffer, "+-{:-<inner_width$}-+", "").unwrap();
47. writeln!(buffer, "| {:^inner_width$} |", &self.title).unwrap();
48. writeln!(buffer, "+={:=<inner_width$}=+", "").unwrap();
49. for line in inner.lines() {
50. writeln!(buffer, "| {:inner_width$} |", line).unwrap();
51. }
52. writeln!(buffer, "+-{:-<inner_width$}-+", "").unwrap();
53. }
54. }
1. // ---- src/main.rs ----
2. mod widgets;

4. use widgets::Widget;

6. fn main() {
7. let mut window = widgets::Window::new("Rust GUI Demo 1.23");
8. window
9. .add_widget(Box::new(widgets::Label::new("This is a small text GUI demo.")));
10. window.add_widget(Box::new(widgets::Button::new("Click me!")));
11. window.draw();
12. }