Rust 애플리케이션용 GUI 만들기

대부분의 초보 프로그래머는 놀랍고 인기 있는 무언가를 만들고 싶고 언젠가는 유명해지고 부자가 되기를 원한다고 생각합니다. 그러나 시작할 때 단말기에서 나오는 "검은 화면"은 새로운 Facebook처럼 보이지 않습니다. 그렇다면 보다 사용자 친화적인 데스크톱 애플리케이션을 구축하려면 어떻게 해야 할까요? 그래픽 사용자 인터페이스로 애플리케이션 구축!

코드 내에서 직접 작업하지 않고 시각적 인터페이스를 생성하기 위해 Glade이라는 애플리케이션을 사용합니다. 구성 요소를 드래그 앤 드롭하고 인터페이스에 대한 구성 요소, 위치, 정보 등으로 XML을 생성하기만 하면 GTK UI를 쉽게 구축할 수 있습니다.

필수 크레이트는 GTK 및 GIO입니다. 프로젝트에서 Cargo.toml 파일이나 Cargo.toml 파일에 이것을 추가하기만 하면 됩니다.

[dependencies.gtk]
version = "0.9.0"
features = ["v3_16"]

[dependencies.gio]
version = ""
features = ["v2_44"]


우리가 만들 예제 응용 프로그램은 다음과 같습니다. 이 색상의 이름을 지정합니다. 이를 통해 사용자는 색상을 선택하고 원하는 이름을 지정할 수 있습니다. 간단하지만 설명 가능합니다.

따라서 NTC 인터페이스가 있습니다. 나에게는 XML이 너무 많은 것 같습니다... 인간이 보아야 할 것처럼 봅시다.

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.2 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkWindow" id="main_window">
    <property name="width_request">450</property>
    <property name="height_request">300</property>
    <property name="can_focus">False</property>
    <property name="title" translatable="yes">Name this color</property>
    <property name="resizable">False</property>
    <property name="window_position">center</property>
    <child type="titlebar">
      <placeholder/>
    </child>
    <child>
      <object class="GtkFixed">
        <property name="width_request">450</property>
        <property name="height_request">300</property>
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <child>
          <object class="GtkEntry" id="color_name_entry">
            <property name="name">color_name_entry</property>
            <property name="width_request">166</property>
            <property name="height_request">40</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
          </object>
          <packing>
            <property name="x">145</property>
            <property name="y">170</property>
          </packing>
        </child>
        <child>
          <object class="GtkColorButton" id="color_selection">
            <property name="name">color_selection</property>
            <property name="width_request">100</property>
            <property name="height_request">80</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
          </object>
          <packing>
            <property name="x">175</property>
            <property name="y">45</property>
          </packing>
        </child>
        <child>
          <object class="GtkLabel" id="select_color_label">
            <property name="name">select_color_label</property>
            <property name="width_request">100</property>
            <property name="height_request">34</property>
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="label" translatable="yes">Select a color</property>
          </object>
          <packing>
            <property name="x">175</property>
            <property name="y">10</property>
          </packing>
        </child>
        <child>
          <object class="GtkButton" id="save_button">
            <property name="label" translatable="yes">Save</property>
            <property name="name">save_button</property>
            <property name="width_request">100</property>
            <property name="height_request">40</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
          </object>
          <packing>
            <property name="x">175</property>
            <property name="y">250</property>
          </packing>
        </child>
        <child>
          <object class="GtkLabel" id="name_color_label">
            <property name="name">name_color_label</property>
            <property name="width_request">100</property>
            <property name="height_request">41</property>
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="label" translatable="yes">Name this color</property>
          </object>
          <packing>
            <property name="x">175</property>
            <property name="y">135</property>
          </packing>
        </child>
        <child>
          <object class="GtkLabel" id="registered_color_label">
            <property name="name">registered_color_label</property>
            <property name="width_request">120</property>
            <property name="height_request">25</property>
            <property name="can_focus">False</property>
          </object>
          <packing>
            <property name="x">165</property>
            <property name="y">215</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
</interface>


따라서 인간에게는 다음과 같이 보입니다.



그것은 무엇입니까?



너무 간단합니다. 선택한 RGBA의 색상과 이름을 가져와 이름과 색상의 두 벡터가 있는 구조에 저장합니다. 이 구조는 빨강, 녹색, 파랑 및 알파 필드로 정의된 또 다른 구조입니다. 그 후 캡쳐한 이름과 색상을 스트럭처에 푸시합니다.

코드를 보여주는 것이 항상 더 나은 설명입니다.

// src/ntc.rs
#[derive(Debug, PartialEq)]
pub struct Color {
    pub red: f64,
    pub green: f64,
    pub blue: f64,
    pub alpha: f64
}
pub struct NTC {
  pub names: Vec<String>,
  pub colors: Vec<Color>
}
impl NTC {
  pub fn new() -> Self {
    NTC {
      names: vec![],
      colors: vec![]
    }
  }
  pub fn save_color(&mut self, color: Color, name: String) -> Result<(), String> {
    if self.colors.contains(&color) || self.names.contains(&name) {
      Err("The color was already saved!".to_string())
    } else {
      self.colors.push(color);
      self.names.push(name);
      Ok(())
    }
  }
}


기본 코드는 자체적으로 설명됩니다.

// src/main.rs
use std::{cell::RefCell, path::Path, rc::Rc};

// gtk needs
use gtk::prelude::*;
use gio::prelude::*;

use ntc::Color;

mod ntc; // importing the ntc module

fn main() {
    gtk::init() // This function will initialize the gtk
    .expect("Could not init the GTK"); 
    // and if something goes wrong, it will send this message
    /*
    The documentation says about gtk::init and gtk::Application::new:
    "When using Application, it is not necessary to call gtk_init manually. 
    It is called as soon as the application gets registered as the 
    primary instance".
    It worth to check it.
    */

    // Here it defined a gtk application, the minimum to init an application
    // There are some caveats about this
    /*
       To build this interface, I have used a component GtkWindow as father of from 
       all others components, hence, it needed to create Gtk::Application inside 
       de code.

       If a GtkApplicationWindow had been to choose, it would not be necessary, 
       because it alraedy had a Gtk::Applicaiton "inside".
   */
    let application = gtk::Application::new(
        Some("dev.henrybarreto.name-this-color"), // Application id
        Default::default() // Using default flags
    ).expect("Could not create the gtk aplication");

    // The magic happens in this line
    // The ntc.glade is pushed into our code through a builder.
    // With this builder it is possible to get all components inside the XML from Glade
    let builder: gtk::Builder =  gtk::Builder::from_file(Path::new("ntc.glade"));

    // ----------------------------------------------------------|
    let colors_saved = Rc::new(RefCell::new(ntc::NTC::new()));// |
    // ----------------------------------------------------------|

    // when the signal connect_activate was sent, the application will get our
    // components for work
    application.connect_activate(move |_| {
    // All components from the ntc.glade are imported, until the one has not used to
    // for didactic propouses
    // the "method" get_object gets from the id.
        let main_window: gtk::Window = builder.get_object("main_window").expect("Could not get the object main_window");
        let save_button: gtk::Button = builder.get_object("save_button").expect("Could not get the save_button");
        let color_selection: gtk::ColorButton = builder.get_object("color_selection").expect("Could not get the color_selection");
        let color_name_entry: gtk::Entry = builder.get_object("color_name_entry").expect("Could not get the color_name_entry");
        //let _select_color_label: gtk::Label = builder.get_object("select_color_label").expect("Could not get the select_color_label");
        //let _name_color_label: gtk::Label = builder.get_object("name_color_label").expect("Could not get the name_color_label");
        let registered_color_label: gtk::Label = builder.get_object("registered_color_label").expect("Could not get the registeredd_color_label");

        let colors_saved = colors_saved.clone();

    // When the button was clicked...
    // The "main" logic happen here
        save_button.connect_clicked(move |_| {
            let color_rgba = color_selection.get_rgba(); // getting the color from the button
            let color: Color = Color { // setting manually color by color for didactic.
                red: color_rgba.red,
                green: color_rgba.green,
                blue: color_rgba.blue,
                alpha: color_rgba.alpha
            };
            let name = color_name_entry.get_text().to_string(); // getting name from the entry

            registered_color_label.set_visible(true); // Letting the label visible
            if let Ok(()) = colors_saved.borrow_mut().save_color(color, name) { // if the color is saved correctly
                registered_color_label.set_text("Registered!");
            } else { // when does it not
                registered_color_label.set_text("Already Registered!");
            }
        });

    // "event" when the close button is clicked
        main_window.connect_destroy(move |_| {
        // the gtk application is closed
            gtk::main_quit(); 
        });

        main_window.show(); // showing all components inside the main_window
    });

    application.run(&[]); // initializing the application
    gtk::main(); // initializing the gtk looping
}


이 코드의 한 가지 세부 사항은 Rc 및 RefCell의 사용에 대해 설명하는 것이 중요합니다. Rust의 작동 방식과 메모리 관리 시스템을 알면 Fn 특성 함수를 통해 변수 정의를 이동하는 것이 좋은 생각이 아니며 컴파일러에서 허용하지 않는다는 것을 알 수 있습니다.

나는 이 블로그가 단지 Rc와 RefCell에 대해 이야기하기 위한 것임을 인정할 수 있지만, 나는 진실을 말하지 않는 것을 선호합니다.

Rc 및 RefCell



이전 블로그에서 이미 Rc에 대해 언급했지만 학습 제안을 위해 요약하겠습니다. Rc는 Rust에서 단일 값이 여러 소유자를 가질 수 있도록 하는 데 사용됩니다. 여기서 핵심 단어는 "단일 가치"와 "다중 소유자"입니다.

Rc와 달리 RefCell은 하나의 소유자 데이터에 단일 값을 보유합니다. 여기서 핵심 단어는 "단일 값"과 "하나의 소유자 데이터"입니다. 물론 간단하게 설명하려고 하면 문제가 될 수 있으므로 자세한 설명이 포함된 글꼴을 맛보는 것이 좋지만 등록을 위해서도 시도해 보겠습니다.

나는 이것이 일부 차용 규칙에 대해 컴파일러를 속이는 방법이라고 생각하고 이 규칙을 런타임에 확인하도록 합니다. 이 차용 규칙은 이것에 대한 참조가 불변인 경우에도 데이터의 변이와 관련이 있습니다. 이를 위해 내부적으로 안전하지 않은 코드를 사용하지만 "안전하지 않은"주제는 이에 대한 전체 블로그가 될 것입니다.

우리 애플리케이션에서는 이 개념을 사용하여 외부 NTC 구조체를 만들고 클로저 내부의 색상과 이름을 가져오고 이 외부 구조에 대한 가변 참조를 통해 저장했습니다. 다른 프로그래밍 언어에서는 사소한 일이겠지만 Rust는 자신만의 방식으로 작업을 수행합니다... 저를 기쁘게 합니다.

프로젝트는 이렇게 끝납니다.

.
├── Cargo.lock
├── Cargo.toml
├── ntc.glade
└── src
    ├── main.rs
    └── ntc.rs


간단하지 않습니까?

응용 프로그램은 어떻게 생겼습니까?





레지스터가 추가되면...





색상 또는 이름이 "데이터베이스" 내에 이미 존재하는 경우.





이것은 좀 더 개인적인 블로그였습니다. 이 블로그에서 저는 제가 하려고 했던 것과는 반대로 더 "i"가 되려고 노력했지만, 여전히 학습한 내용에 대한 저널과 같고 미래 검토자에게 초록이 됩니다.

읽어 주셔서 감사합니다. 자유롭게 댓글을 달거나 정정하거나 인사만 해주세요. 나는 그것이 누군가를 돕기를 바랍니다.

유용한 링크:
  • https://gtk-rs.org/docs-src/tutorial/glade
  • https://gtk-rs.org/
  • https://doc.rust-lang.org/book/ch15-05-interior-mutability.html
  • https://www.reddit.com/r/rust/comments/755a5x/i_have_finally_understood_what_cell_and_refcell/
  • 좋은 웹페이지 즐겨찾기