练习:面向 GUI 库的模块
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
Rust Playground 仅支持一个文件,因此您需要在本地文件系统上创建一个 Cargo 项目:
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, "+-{:-<inner_width$}-+", "").unwrap();
75. writeln!(buffer, "| {:^inner_width$} |", &self.title).unwrap();
76. writeln!(buffer, "+={:=<inner_width$}=+", "").unwrap();
77. for line in inner.lines() {
78. writeln!(buffer, "| {:inner_width$} |", line).unwrap();
79. }
80. writeln!(buffer, "+-{:-<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, "+{:-<width$}+", "").unwrap();
95. for line in label.lines() {
96. writeln!(buffer, "|{:^width$}|", &line).unwrap();
97. }
98. writeln!(buffer, "+{:-<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.
鼓励学生按照自己认为合适的方式划分代码,并熟悉必需的 mod、use 和 pub 声明。之后,讨论哪些组织方式最符合惯例。
