Создание внешней клавиатуры на Rust


INTRO

Итак, в предыдущем посте мы сделали чистую GTK-клавиатуру с GTK-кнопками. Возможно, это хорошая идея сравнить два способа разработки небольшой клавиатуры.
Теперь мы сделаем то же самое, используя внешний файл разметки, назовем его grid.ui, он слишком большой, чтобы публиковать его здесь. Вы можете просто взять его с моего github.
Если вы такой же новичок, как и я, вы можете спросить: «Зачем нам использовать дополнительный файл, если мы можем использовать только GTK?». Думаю, у нас может быть как минимум две причины:
1) Это помогает перенести часть GUI в markdown файл, чтобы сделать наш код легко читаемым.
2) Некоторые функции работают по-разному в разных случаях, и интересно, как мы можем это использовать.

Кстати: чистые кнопки GTK не масштабируются по умолчанию, но эти масштабируются.

Некоторые вещи я уже объяснял в предыдущем посте, поэтому просто покажу, что нам нужно в нашем проекте.

Откройте Cargo.toml и добавьте список ящиков

[dependencies]
gtk = "0.15.5"
glib = "0.15.10"
chrono = "0.4.19"
Войдите в полноэкранный режим Выйти из полноэкранного режима

Мы не будем изменять файл main.rs

use gtk::prelude::*;

mod buttons;

fn main() {
    let application =
        gtk::Application::new(Some("com.github.gtk-rs.examples.grid"), Default::default());

    application.connect_activate(buttons::build_ui);
    application.run();
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Мы запускаем файл buttons.rs всего с несколькими строками кода.

use gtk::prelude::*;
use gtk::{ApplicationWindow, Builder};

pub fn build_ui(application: &gtk::Application) {
    let glade_src = include_str!("grid.ui");
    let builder = Builder::from_string(glade_src);

    let window: ApplicationWindow = builder.object("window").expect("Couldn't get window");
    window.set_application(Some(application));

    // We are gonna add our buttons and functions here

    window.show_all();
}
Вход в полноэкранный режим Выход из полноэкранного режима

Если вы cargo run его запустите, то увидите все элементы, присоединенные к сетке, потому что все они созданы в файле markdown src/grid.ui. Все, что нам нужно — просто подключить этот файл

let glade_src = include_str!("grid.ui");
Войти в полноэкранный режим Выйти из полноэкранного режима

Затем отправьте glade_src в Builder как строку slice &str.

let builder = Builder::from_string(glade_src);
Вход в полноэкранный режим Выйти из полноэкранного режима

И подключитесь к ApplicationWindow

let window: ApplicationWindow = builder.object("window").expect("Couldn't get window");
Войти в полноэкранный режим Выйдите из полноэкранного режима

Еще раз:
1) Создайте файл markdown со всеми необходимыми элементами
2) Привяжите его к переменной
3) Отправьте эту переменную в GTK builder
4) Подключите его к ApplicationWindow

Давайте снова посмотрим на чистый GTK (не добавляйте этот код в наш текущий проект)

    let window = gtk::ApplicationWindow::new(application);
    window.set_title("Studying GTK");
    window.set_default_size(200, 120);
    let grid = gtk::Grid::builder()
        .margin_start(7)
        .margin_end(7)
        .margin_top(7)
        .margin_bottom(7)
        .halign(gtk::Align::Center)
        .valign(gtk::Align::Center)
        .row_spacing(7)
        .column_spacing(7)
        .build();
    window.set_child(Some(&grid));
Вход в полноэкранный режим Выход из полноэкранного режима

Видите разницу? Мы должны создать окно, затем сделать сетку и подключиться к нашему окну. Давайте вернемся к нашему текущему проекту.
Мы используем файл markdown, и он должен выглядеть так, как показано на скриншоте ниже. Это куча бесполезных кнопок, потому что Rust не знает об их существовании. Как я уже говорил — это просто строковый срез содержимого нашего файла markdown. Мы должны объяснить Rust, как его использовать. И здесь нам нужен идентификатор класса.

<object class="GtkButton" id="button0">
Вход в полноэкранный режим Выход из полноэкранного режима

Давайте рассмотрим это подробнее в следующей главе.

1. Скользящая кнопка

Обновите зависимости и давайте подключим сетку.

use gtk::glib;
use gtk::prelude::*;
use gtk::{ApplicationWindow, Builder};
use gtk::{Button, Grid};
Вход в полноэкранный режим Выход из полноэкранного режима

Сначала нам нужна переменная типа Grid. Она вызывает gtk::Builder и метод object(), который получает некоторую "grid". Что это такое?

    let grid: Grid = builder.object("grid").expect("Couldn't get grid");
Вход в полноэкранный режим Выход из полноэкранного режима

Это класс и id. Класс говорит о том, что это за элемент. Id — это уникальный идентификатор именно этого элемента. Например, это может быть элемент типа "GtkGrid" с id "grid".

<object class="GtkGrid" id="grid">
Вход в полноэкранный режим Выход из полноэкранного режима

Как вы помните, мы говорили Builder, что наш GUI хранится в файле grid.ui.

let glade_src = include_str!("grid.ui");
let builder = Builder::from_string(glade_src);
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь gtk::Builder может найти элемент по его id внутри markdown файла и привязать к нему функцию. И мы сделаем это прямо сейчас.
Давайте создадим кнопку button0 и вызовем ее по id "button0".

    let button0: Button = builder.object("button0").expect("Couldn't get button0");
Вход в полноэкранный режим Выйти из полноэкранного режима

Теперь мы можем добавить к ней функцию, вызвав connect_clicked().

    button0.connect_clicked(glib::clone!(@weak grid => move |button| {
        let left_attach = grid.cell_left_attach(button);
        let new_left_attach = if left_attach == 2 { 0 } else { left_attach + 1 };
        grid.set_cell_left_attach(button, new_left_attach);
    }));

    window.show_all();
}
Вход в полноэкранный режим Выход из полноэкранного режима

Мы используем glib::clone! для создания клона и @weak или @strong (похоже, оба делают одно и то же в данном случае), чтобы сообщить Rust, какой тип клона мы хотим использовать. Затем мы перемещаем кнопку типа в закрытие и возвращаем ее в конце с новой позицией в сетке.
Здесь мы объявляем две переменные: left_attach и new_left_attach. После запуска приложения первая переменная содержит 1.
Значение new_left_attach зависит от условия if else. Каждый щелчок мыши увеличивает left_attach на 1. Как только оно становится равным 2, условие else делает его равным 0. Попробуйте изменить условия,

<property name="left_attach">1</property>
Войдите в полноэкранный режим Выход из полноэкранного режима

и ширину кнопки 0.
Хорошо, теперь кнопка 0 сдвигается вправо каждый раз, когда мы нажимаем на нее.

2. Числовые кнопки

Я говорил вам, что gtk::Builder может привязывать функцию к id элемента, но он также может отправлять данные. Давайте добавим числовые кнопки и заставим их менять свои собственные метки.
Соедините кнопки, используя их id

    let button1: Button = builder.object("button1").expect("Couldn't get button1");
    let button2: Button = builder.object("button2").expect("Couldn't get button2");
    let button3: Button = builder.object("button3").expect("Couldn't get button3");
    let button4: Button = builder.object("button4").expect("Couldn't get button4");
    let button5: Button = builder.object("button5").expect("Couldn't get button5");
    let button6: Button = builder.object("button6").expect("Couldn't get button6");
    let button7: Button = builder.object("button7").expect("Couldn't get button7");
    let button8: Button = builder.object("button8").expect("Couldn't get button8");
    let button9: Button = builder.object("button9").expect("Couldn't get button9");
Вход в полноэкранный режим Выйти из полноэкранного режима

Добавьте функцию, которая после нажатия на кнопку установит ее метку в числовую букву. Обратите внимание, что в этом случае нам не нужно использовать фигурные скобки. Также нам не нужен glib::clone! потому что мы не перемещаем никаких данных внутри закрытия. Метод set_label() устанавливает новую метку.

    button1.connect_clicked( move |button| button.set_label("I") );
    button2.connect_clicked( move |button| button.set_label("II") );
    button3.connect_clicked( move |button| button.set_label("III") );
    button4.connect_clicked( move |button| button.set_label("IV") );
    button5.connect_clicked( move |button| button.set_label("V") );
    button6.connect_clicked( move |button| button.set_label("VI") );
    button7.connect_clicked( move |button| button.set_label("VII") );
    button8.connect_clicked( move |button| button.set_label("VIII") );
    button9.connect_clicked( move |button| button.set_label("IX") );
Вход в полноэкранный режим Выход из полноэкранного режима

Запустите и посмотрите! Цифровые кнопки 1-9 меняют свои метки после первого нажатия.

3. Кнопка «Выход

Другая зависимость

use glib::clone;
Вход в полноэкранный режим Выход из полноэкранного режима

В этом случае @weak и @strong — одно и то же. Возможно. Все еще не уверен, как это работает.
Объявляем quit_button с типом Button. Обратите внимание на имя object()! Здесь мы вызываем объект по его id. В предыдущей главе |button| был типом, а не данными. Но теперь нам нужен glib::clone!, потому что мы перемещаем окно внутрь закрытия, чтобы уничтожить его.

    let quit_button: Button = builder.object("quit_button").expect("Couldn't get quit_button");
    quit_button.connect_clicked(clone!(@weak window => move |_|
        unsafe {
            window.destroy()
        }
    ));
Вход в полноэкранный режим Выход из полноэкранного режима

Давайте сравним это с проектом без markdown-файла из моего предыдущего сообщения (не добавляйте этот код в наш текущий проект). Как вы можете видеть, здесь мы создаем сами кнопки.

let quit_button = gtk::Button::with_label("Quit");
Вход в полноэкранный режим Выход из полноэкранного режима

Функция подключения, которая полностью аналогична предыдущей…

    quit_button.connect_clicked(clone!(@weak window => move |_| 
        unsafe {
            window.destroy()
        }
    ));
Вход в полноэкранный режим Выход из полноэкранного режима

и прикрепление его к сетке.

grid.attach(&quit_button, 3, 1, 1, 4);
Войти в полноэкранный режим Выход из полноэкранного режима

Разница: 1) декларирование 2) прикрепление

4. Обновление метки

Теперь щелкните на кнопке, чтобы сделать метку, принимающую данные.
Надеюсь, вы заметили, что когда мы используем внешний файл разметки, необходимы аннотации типов. Мы должны сказать компилятору — это GTK-объект типа Label.

let counter_label: gtk::Label = builder.object("GtkLabel_1").expect("Couldn't get GtkLabel_1");
Вход в полноэкранный режим Выход из полноэкранного режима

Объявление minus_button и plus_button такое же, как и раньше, ничего особенного.

let minus_button: Button = builder.object("minus").expect("Couldn't get minus");
let plus_button: Button = builder.object("plus").expect("Couldn't get plus");
Вход в полноэкранный режим Выход из полноэкранного режима

Функции будут на сто процентов такими же, как мы сделали в чистом проекте GTK.

    plus_button.connect_clicked(glib::clone!(@weak counter_label => move |_| {
        let nb = counter_label.text()
            .parse()
            .unwrap_or(0.0);
        counter_label.set_text(&format!("{}", nb + 1.1));
    }));
    minus_button.connect_clicked(glib::clone!(@weak counter_label => move |_| {
        let nb = counter_label.text()
            .parse()
            .unwrap_or(0.0);
        counter_label.set_text(&format!("{}", nb - 1.2));
    }));
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь кнопки работают и отправляют данные на ярлык.

5. Таймер

Введите новый путь в область видимости.

use chrono::Local;
Вход в полноэкранный режим Выход из полноэкранного режима

В конце список путей выглядит следующим образом.

use gtk::glib;
use glib::clone;
use gtk::prelude::*;
use gtk::{ApplicationWindow, Builder};
use gtk::{Button, Grid};
use chrono::Local;
Войти в полноэкранный режим Выйти из полноэкранного режима

Объявим переменную времени, как мы делали это в предыдущем проекте.

let time = format!("{}", Local::now().format("%Y-%m-%d %H:%M:%S"));
Войти в полноэкранный режим Выход из полноэкранного режима

Но эта часть отличается!

let label_time: gtk::Label = builder.object("GtkLabel_2").expect("Couldn't get GtkLabel_2");
label_time.set_text(&time);
Вход в полноэкранный режим Выйти из полноэкранного режима

Просто сравните с аналогичной частью в предыдущем проекте (не добавляйте этот код в наш текущий проект).

let label_time = gtk::Label::new(None);
label_time.set_text(&time);
grid.attach(&label_time, 0, 6, 4, 1);
Вход в полноэкранный режим Выход из полноэкранного режима

Это то же самое, что и в случае с чистой сеткой GTK.

    let tick = move || {
        let time = format!("{}", Local::now().format("%Y-%m-%d %H:%M:%S"));
        label_time.set_text(&time);
        // What is this?
        glib::Continue(true)
    };
    glib::timeout_add_seconds_local(1, tick);
Вход в полноэкранный режим Выйти из полноэкранного режима

Теперь запустите его и вы увидите следующее. Все кнопки работают нормально. Осталась только одна маленькая деталь…

6. Вход не работает!

Согласно документации Entry должен выглядеть так, но, к сожалению, мне не удалось заставить его работать.

<object class="GtkEntry">
  <attributes>
    <attribute name="weight" value="PANGO_WEIGHT_BOLD"/>
    <attribute name="background" value="red" start="5" end="10"/>
  </attributes>
</object>
Вход в полноэкранный режим Выйти из полноэкранного режима

Я потратил некоторое время, пытаясь решить эту загадку. Возможно, я вернусь к этому вопросу позже.

ЗАКЛЮЧЕНИЕ

Здесь еще есть несколько вещей, которые я пока не совсем понимаю. А также есть некоторые идеи, которые я не могу реализовать прямо сейчас. Например, встроенный дисплей или кнопка сохранения файла. Но я думаю, что постепенно разберусь и с этим.
Надеюсь, этот пост был полезен и познавателен.
Берегите себя

Оцените статью
devanswers.ru
Добавить комментарий