Создание структуры пользовательского интерфейса

TODO: вставить ссылку на картинку

Реализация модуля пользовательского интерфейса (ui/mod.rs)

Теперь, когда относящийся к UI код перемещен в свой собственный модуль, очень важно объявить подмодули, которые данный модуль будет импортировать. Также важно объявить типы и функции, который данный модуль будет экспортировать.


# #![allow(unused_variables)]
#fn main() {
mod app;
mod content;
mod dialogs;
mod header;
pub mod misc;
pub mod save;

pub use self::app::App;
pub use self::content::Content;
pub use self::dialogs::{OpenDialog, SaveDialog};
pub use self::header::Header;
#}

Как можно заметить выше, мы создаем следующие модули: app.rs, content.rs, dialogs.rs, save.rs, header.rs, misc.rs.

Реализация структуры программы (ui/app.rs)

Данная часть должна быть более-менее простой, так как использует код, подобный тому, что был задействован в предыдущих главах. С той разницей, что мы переместили инициализацию GTK в начало App.


# #![allow(unused_variables)]
#fn main() {
use gtk;
use gtk::*;
use super::Header;
use super::Content;

pub struct App {
    pub window:  Window,
    pub header:  Header,
    pub content: Content,
}

impl App {
    pub fn new() -> App {
        // Инициализация GTK.
        if gtk::init().is_err() {
            eprintln!("failed to initialize GTK Application");
            process::exit(1);
        }

        // Создать окно верхнего уровня.
        let window = Window::new(WindowType::Toplevel);
        // Создать заголовочную панель и связанное с ней содержимое.
        let header = Header::new();
        // Создать контейнер для хранения содержимого и виджетов.
        let content = Content::new();

        // Сделать заголовочную панель виджетом, содержащим название.
        window.set_titlebar(&header.container);
        // Установить заголовок окна.
        window.set_title("Markdown Editor");
        // Установить класс менеджера окна.
        window.set_wmclass("md-editor", "Markdown Editor");
        // Иконка программы.
        window.set_default_size(800, 600);
        Window::set_default_icon_name("iconname");
        // Добавить содержимое вовнутрь окна.
        window.add(&content.container);

        // Запрограммировать поведение кнопки выхода.
        window.connect_delete_event(move |_, _| {
            main_quit();
            Inhibit(false)
        });

        // Возвратить структуру программы.
        App { window, header, content }
    }
}
#}

Реализация структуры заголовка (ui/header.rs)

Данную структуру будет реализовать ещё проще. Мы реализуем кнопки Open, Save, Save As, которые будут находиться в заголовке. Мы будем использовать мнемоники, так что одна из букв в названии кнопок будет становиться подчеркнутой при нажатии на клавишу Alt, так что пользователь сможет выбрать нужную кнопку.


# #![allow(unused_variables)]
#fn main() {
use gtk::*;

pub struct Header {
    pub container: HeaderBar,
    pub open:      Button,
    pub save:      Button,
    pub save_as:   Button,
}

impl Header {
    pub fn new() -> Header {
        // Создать контейнер для хранения главной заголовочной панели. 
        let container = HeaderBar::new();

        // Установить текст, который будет показывать в заголовке
        // заголовочной панели.
        container.set_title("Markdown Editor");
        // Включить элементы управления окна внутри заголовочной панели.
        container.set_show_close_button(true);

        let open = Button::new_with_mnemonic("_Open");
        let save = Button::new_with_mnemonic("_Save");
        let save_as = Button::new_with_mnemonic("Save _As");
        container.pack_start(&open);
        container.pack_end(&save_as);
        container.pack_end(&save);

        // Возвратить заголовок и все его внутреннее содержимое.
        Header { container, open, save, save_as }
    }
}
#}

Реализация структуры содержимого (ui/content.rs)

Здесь мы начнем использовать GtkWebViews, GtkSourceViews.

Создание просмотра web

Создание просмотра web очень легко создать. Выполняя следующий код, вы получите панель для просмотра web, которую вы можете внедрить в ваш пользовательский интерфейс так же, как вы можете это сделать с другими виджетами.


# #![allow(unused_variables)]
#fn main() {
// Create a the WebView for the preview pane.
let context = WebContext::get_default().unwrap();
let preview = WebView::new_with_context(&context);
#}

Данный просмотр web прокручиваем, так что эту функциональность не нужно реализовывать самостоятельно.

Создаем и настраиваем просмотр кода

Просмотр кода являются более сложной реализацией, потому что он требуют дополнительной настройки. Мы попытаемся получить следующий результат:

  • Табуляция должна быть размером в 4 пробела
  • Просмотрщик должен показывать номера строк
  • Будем использовать шрифт по умолчанию - monospaced, размер - 11
  • Подсветка синтаксиса Markdown
  • Попытка использовать тему Builder, если не получится - тему Classic

Начнем со структуры Source.


# #![allow(unused_variables)]
#fn main() {
pub struct Source {
    pub container: ScrolledWindow,
    pub view:      View,
    pub buff:      Buffer,
}

impl Source {
    fn new() -> Source {

    }
}
#}

После этого создадим буфер и представление для просмотра, это делается так же, как и создание простого текстового буфера и представления для просмотра. Как только это сделано, мы поместим представление вовнутрь прокручиваемого окна.


# #![allow(unused_variables)]
#fn main() {
// Создать SourceView для редактора на левой панели.
let buff = Buffer::new(None);
let view = View::new_with_buffer(&buff);
let container = ScrolledWindow::new(None, None);
container.add(&view);
#}

Установим настройки, используя функцию, принимающую представление и буфер:


# #![allow(unused_variables)]
#fn main() {
fn configure_source_view(view: &View, buff: &Buffer) {
    view.set_show_line_numbers(true);
    view.set_monospace(true);
    view.set_insert_spaces_instead_of_tabs(true);
    view.set_indent_width(4);
    view.set_smart_backspace(true);
    view.set_right_margin(100);
    view.set_left_margin(10);
    view.set_show_right_margin(true);
    view.set_background_pattern(BackgroundPatternType::Grid);
    // TODO: следующий релиз пакета GTK
    // view.set_input_hints(InputHints::SPELLCHECK + InputHints::WORD_COMPLETION);
}
#}

Мы можем использовать пакет pango для изменения шрифта в представлении. Заметьте, нам необходимо объявить типаж, чтобы понять откуда появился метод override_font. Возможно, будущее обновление GTK Rust API займется решением этого вопроса.


# #![allow(unused_variables)]
#fn main() {
// Настраивает шрифт, используемый в панели с кодом. По умолчанию используется
// Monospaced, размер - 11. Когда будем изменять шрифт, нужно вручную указать
// типаж, в котором находится нужный метод.
let font = FontDescription::from_string("monospace 11");
WidgetExt::override_font(&view, &font);
#}

Включим подсветку синтаксиса Markdown по умолчанию. Используемые языки извлекаются из LanguageManager. Язык будет присвоен напрямую буферу, а не представлению.


# #![allow(unused_variables)]
#fn main() {
// Включить подсветку Markdown
LanguageManager::new()
    .get_language("markdown")
    .map(|markdown| buff.set_language(&markdown));
#}

Укажем используемую схему.


# #![allow(unused_variables)]
#fn main() {
let manager = StyleSchemeManager::new();
    manager
        .get_scheme("Builder")
        .or(manager.get_scheme("Classic"))
        .map(|theme| buff.set_style_scheme(&theme));
#}

Полный исходный код


# #![allow(unused_variables)]
#fn main() {
use gtk::*;
use pango::*;
use sourceview::*;
use webkit2gtk::*;

pub struct Content {
    pub container: Paned,
    pub source:    Source,
    pub preview:   WebView,
}

pub struct Source {
    pub container: ScrolledWindow,
    pub view:      View,
    pub buff:      Buffer,
}

impl Content {
    pub fn new() -> Content {
        // Создать контейнер для хранения главного содержимого
        let container = Paned::new(Orientation::Horizontal);
        let source = Source::new();

        // Создать WebView для предыдущей панели.
        let context = WebContext::get_default().unwrap();
        let preview = WebView::new_with_context(&context);

        // Упакуем
        container.pack1(&source.container, true, true);
        container.pack2(&preview, true, true);

        // Сделать так, чтобы две панели имели одинаковый размер - половина
        // контейнера, в котором они находятся.
        source.container.set_size_request(100, -1);
        preview.set_size_request(100, -1);

        Content { container, source, preview }
    }
}

impl Source {
    pub fn new() -> Source {
        // Создать SourceView для редактора на левой панели.
        let buff = Buffer::new(None);
        let view = View::new_with_buffer(&buff);
        let container = ScrolledWindow::new(None, None);
        container.add(&view);

        configure_source_view(&view, &buff);

        Source { container, buff, view }
    }
}

fn configure_source_view(view: &View, buff: &Buffer) {
    WidgetExt::override_font(view, &FontDescription::from_string("monospace"));

    LanguageManager::new()
        .get_language("markdown")
        .map(|markdown| buff.set_language(&markdown));

    let manager = StyleSchemeManager::new();
    manager
        .get_scheme("Builder")
        .or(manager.get_scheme("Classic"))
        .map(|theme| buff.set_style_scheme(&theme));

    view.set_show_line_numbers(true);
    view.set_monospace(true);
    view.set_insert_spaces_instead_of_tabs(true);
    view.set_indent_width(4);
    view.set_smart_backspace(true);
    view.set_right_margin(100);
    view.set_left_margin(10);
    view.set_show_right_margin(true);
    view.set_background_pattern(BackgroundPatternType::Grid);
    // TODO: следующий релиз пакета GTK
    // view.set_input_hints(InputHints::SPELLCHECK + InputHints::WORD_COMPLETION);
}
#}