تمرین: ماژولهایی برای کتابخانه رابط کاربری گرافیکی

در این تمرین، یک پیاده‌سازی کتابخانه GUI کوچک را دوباره سازماندهی خواهید کرد. این کتابخانه یک ویژگیWidget و چند پیاده‌سازی از آن ویژگی و همچنین یک تابع main را تعریف می‌کند.

معمول است که هر نوع یا مجموعه‌ای از انواع مرتبط نزدیک را در ماژول خود قرار دهید، بنابراین هر نوع ویجت باید ماژول خاص خود را داشته باشد.

Cargo Setup

یک Rust playground فقط از یک فایل پشتیبانی می‌کند، بنابراین باید یک پروژه Cargo را در سیستم فایل محلی خود ایجاد کنید:

cargo init gui-modules
cd gui-modules
cargo run

این src/main.rs حاصل را ویرایش کنید تا عبارات mod را اضافه کنید و فایل‌های اضافی را در دایرکتوری src اضافه کنید.

منبع

در اینجا اجرای تک ماژول کتابخانه GUI آمده است:

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

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

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

pub struct Label {
    label: String,
}

impl Label {
    fn new(label: &str) -> Label {
        Label { label: label.to_owned() }
    }
}

pub struct Button {
    label: Label,
}

impl Button {
    fn new(label: &str) -> Button {
        Button { label: Label::new(label) }
    }
}

pub struct Window {
    title: String,
    widgets: Vec<Box<dyn Widget>>,
}

impl Window {
    fn new(title: &str) -> Window {
        Window { title: title.to_owned(), widgets: Vec::new() }
    }

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

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

impl Widget for Window {
    fn width(&self) -> usize {
        // Add 4 paddings for borders
        self.inner_width() + 4
    }

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

        let inner_width = self.inner_width();

        // TODO: Change draw_into to return Result<(), std::fmt::Error>. Then use the
        // ?-operator here instead of .unwrap().
        writeln!(buffer, "+-{:-<inner_width$}-+", ".").unwrap();
        writeln!(buffer, "| {:^inner_width$} |", &self.title).unwrap();
        writeln!(buffer, "+={:=<inner_width$}=+", ".").unwrap();
        for line in inner.lines() {
            writeln!(buffer, "| {:inner_width$} |", line).unwrap();
        }
        writeln!(buffer, "+-{:-<inner_width$}-+", ".").unwrap();
    }
}

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

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

        writeln!(buffer, "+{:-<width$}+", ".").unwrap();
        for line in label.lines() {
            writeln!(buffer, "|{:^width$}|", &line).unwrap();
        }
        writeln!(buffer, "+{:-<width$}+", ".").unwrap();
    }
}

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

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

fn main() {
    let mut window = Window::new("Rust GUI Demo 1.23");
    window.add_widget(Box::new(Label::new("این یک نسخه نمایشی GUI برای متنی کوچک است.")));
    window.add_widget(Box::new(Button::new("Click me!")));
    window.draw();
}
This slide and its sub-slides should take about 15 minutes.

دانش‌آموزان را تشویق کنید تا کد را به‌گونه‌ای تقسیم کنند که برایشان طبیعی است و به اعلان‌های mod, use و pubعادت کنند. پس از آن، در مورد اینکه چه organizationهایی idiomatic هستند بحث کنید.