Exercise: Modules for a GUI Library

In this exercise, you will reorganize a small GUI Library implementation. This library defines a Widget trait and a few implementations of that trait, as well as a main function.

It is typical to put each type or set of closely-related types into its own module, so each widget type should get its own module.

Cargo Setup

The Rust playground only supports one file, so you will need to make a Cargo project on your local filesystem:

1. cargo init gui-modules
2. cd gui-modules
3. cargo run

Edit the resulting src/main.rs to add mod statements, and add additional files in the src directory.

Source

Here’s the single-module implementation of the GUI library:

1. ```
   pub trait Widget {
   ```
2. ```
       /// Natural width of `self`.
   ```
3. ```
       fn width(&self) -> usize;
   ```

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

8. ```
       /// Draw the widget on standard output.
   ```
9. ```
       fn draw(&self) {
   ```
10. ```
            let mut buffer = String::new();
    ```
11. ```
            self.draw_into(&mut buffer);
    ```
12. ```
            println!("{buffer}");
    ```
13. ```
        }
    ```
14. ```
    }
    ```

16. ```
    pub struct Label {
    ```
17. ```
        label: String,
    ```
18. ```
    }
    ```

20. ```
    impl Label {
    ```
21. ```
        fn new(label: &str) -> Label {
    ```
22. ```
            Label { label: label.to_owned() }
    ```
23. ```
        }
    ```
24. ```
    }
    ```

26. ```
    pub struct Button {
    ```
27. ```
        label: Label,
    ```
28. ```
    }
    ```

30. ```
    impl Button {
    ```
31. ```
        fn new(label: &str) -> Button {
    ```
32. ```
            Button { label: Label::new(label) }
    ```
33. ```
        }
    ```
34. ```
    }
    ```

36. ```
    pub struct Window {
    ```
37. ```
        title: String,
    ```
38. ```
        widgets: Vec<Box<dyn Widget>>,
    ```
39. ```
    }
    ```

41. ```
    impl Window {
    ```
42. ```
        fn new(title: &str) -> Window {
    ```
43. ```
            Window { title: title.to_owned(), widgets: Vec::new() }
    ```
44. ```
        }
    ```

46. ```
        fn add_widget(&mut self, widget: Box<dyn Widget>) {
    ```
47. ```
            self.widgets.push(widget);
    ```
48. ```
        }
    ```

50. ```
        fn inner_width(&self) -> usize {
    ```
51. ```
            std::cmp::max(
    ```
52. ```
                self.title.chars().count(),
    ```
53. ```
                self.widgets.iter().map(|w| w.width()).max().unwrap_or(0),
    ```
54. ```
            )
    ```
55. ```
        }
    ```
56. ```
    }
    ```

58. ```
    impl Widget for Window {
    ```
59. ```
        fn width(&self) -> usize {
    ```
60. ```
            // Add 4 paddings for borders
    ```
61. ```
            self.inner_width() + 4
    ```
62. ```
        }
    ```

64. ```
        fn draw_into(&self, buffer: &mut dyn std::fmt::Write) {
    ```
65. ```
            let mut inner = String::new();
    ```
66. ```
            for widget in &self.widgets {
    ```
67. ```
                widget.draw_into(&mut inner);
    ```
68. ```
            }
    ```

70. ```
            let inner_width = self.inner_width();
    ```

72. ```
            // TODO: Change draw_into to return Result<(), std::fmt::Error>. Then use the
    ```
73. ```
            // ?-operator here instead of .unwrap().
    ```
74. ```
            writeln!(buffer, "+-{:-&lt;inner_width$}-+", "").unwrap();
    ```
75. ```
            writeln!(buffer, "| {:^inner_width$} |", &self.title).unwrap();
    ```
76. ```
            writeln!(buffer, "+={:=&lt;inner_width$}=+", "").unwrap();
    ```
77. ```
            for line in inner.lines() {
    ```
78. ```
                writeln!(buffer, "| {:inner_width$} |", line).unwrap();
    ```
79. ```
            }
    ```
80. ```
            writeln!(buffer, "+-{:-&lt;inner_width$}-+", "").unwrap();
    ```
81. ```
        }
    ```
82. ```
    }
    ```

84. ```
    impl Widget for Button {
    ```
85. ```
        fn width(&self) -> usize {
    ```
86. ```
            self.label.width() + 8 // add a bit of padding
    ```
87. ```
        }
    ```

89. ```
        fn draw_into(&self, buffer: &mut dyn std::fmt::Write) {
    ```
90. ```
            let width = self.width();
    ```
91. ```
            let mut label = String::new();
    ```
92. ```
            self.label.draw_into(&mut label);
    ```

94. ```
            writeln!(buffer, "+{:-&lt;width$}+", "").unwrap();
    ```
95. ```
            for line in label.lines() {
    ```
96. ```
                writeln!(buffer, "|{:^width$}|", &line).unwrap();
    ```
97. ```
            }
    ```
98. ```
            writeln!(buffer, "+{:-&lt;width$}+", "").unwrap();
    ```
99. ```
        }
    ```
100. ```
     }
     ```

102. ```
     impl Widget for Label {
     ```
103. ```
         fn width(&self) -> usize {
     ```
104. ```
             self.label.lines().map(|line| line.chars().count()).max().unwrap_or(0)
     ```
105. ```
         }
     ```

107. ```
         fn draw_into(&self, buffer: &mut dyn std::fmt::Write) {
     ```
108. ```
             writeln!(buffer, "{}", &self.label).unwrap();
     ```
109. ```
         }
     ```
110. ```
     }
     ```

112. ```
     fn main() {
     ```
113. ```
         let mut window = Window::new("Rust GUI Demo 1.23");
     ```
114. ```
         window.add_widget(Box::new(Label::new("This is a small text GUI demo.")));
     ```
115. ```
         window.add_widget(Box::new(Button::new("Click me!")));
     ```
116. ```
         window.draw();
     ```
117. ```
     }
     ```

This slide and its sub-slides should take about 15 minutes.

Encourage students to divide the code in a way that feels natural for them, and get accustomed to the required mod, use, and pub declarations. Afterward, discuss what organizations are most idiomatic.