Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Making external keyboard on Rust
Antonov Mike
Antonov Mike

Posted on

     

Making external keyboard on Rust

INTRO

Ok we made pure GTK keyboard with GTK buttons inprevious post. Maybe it is a good idea to compare two ways of developing a small keyboard.
Now we gonna make the same using external markdown file, let’s call itgrid.ui and it is too huge to post it here. You can just grab it from mygithub.
If you are rookie like me you can ask “why should we use extra file if we can use GTK only?” I guess we may have at least two reasons:
1) It helps to move some part of GUI stuff to the markdown file to make our code easy to read.
2) Some functions work different in different cases, and it’s interesting how we can use it.

By the way: pure GTK buttons are not scalable by default, but this ones are.

Some things I’ve explained in the previous post, so I just show you what we need in our project.

Open Cargo.toml and add list of crates

[dependencies]gtk="0.15.5"glib="0.15.10"chrono="0.4.19"
Enter fullscreen modeExit fullscreen mode

We won’t changemain.rs file

usegtk::prelude::*;modbuttons;fnmain(){letapplication=gtk::Application::new(Some("com.github.gtk-rs.examples.grid"),Default::default());application.connect_activate(buttons::build_ui);application.run();}
Enter fullscreen modeExit fullscreen mode

We are startingbuttons.rs with only few lines of code.

usegtk::prelude::*;usegtk::{ApplicationWindow,Builder};pubfnbuild_ui(application:&gtk::Application){letglade_src=include_str!("grid.ui");letbuilder=Builder::from_string(glade_src);letwindow:ApplicationWindow=builder.object("window").expect("Couldn't get window");window.set_application(Some(application));// We are gonna add our buttons and functions herewindow.show_all();}
Enter fullscreen modeExit fullscreen mode

If youcargo run it you will see all the elements attached to the grid, because all of them are created in a markdownsrc/grid.ui file. All we need – just connect this file

letglade_src=include_str!("grid.ui");
Enter fullscreen modeExit fullscreen mode

Then sendglade_src to Builder as a string slice&str.

letbuilder=Builder::from_string(glade_src);
Enter fullscreen modeExit fullscreen mode

And connect to ApplicationWindow

letwindow:ApplicationWindow=builder.object("window").expect("Couldn't get window");
Enter fullscreen modeExit fullscreen mode

One more time:
1) Create markdown file with all elements you need
2) Bind it to variable
3) Send this variable to GTK builder
4) Connect it with ApplicationWindow

Let’s look at pure GTK again (do not add this code into our current project)

letwindow=gtk::ApplicationWindow::new(application);window.set_title("Studying GTK");window.set_default_size(200,120);letgrid=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));
Enter fullscreen modeExit fullscreen mode

See the difference? We have to create window, then make grid and connect to our window. Let’s get back to our current project.
We are using markdown file and it should look like on the screenshot below. This is a bunch of useless buttons because Rust doesn’t know they exist. As I said before – it’s just a string slice of our markdown file content. We have to explain Rust how to use it. And here we need class id.

<objectclass="GtkButton"id="button0">
Enter fullscreen modeExit fullscreen mode

Let’s look at it closer in the next chapter.

Screenshot №1

1. Sliding button

Update your dependencies and let’s connect a grid.

usegtk::glib;usegtk::prelude::*;usegtk::{ApplicationWindow,Builder};usegtk::{Button,Grid};
Enter fullscreen modeExit fullscreen mode

First we need a variable of type Grid. It callsgtk::Builder and methodobject() which receives some“grid”. What is that?

letgrid:Grid=builder.object("grid").expect("Couldn't get grid");
Enter fullscreen modeExit fullscreen mode

This is the class and id. Class tells what kind of element it is. Id is a unique identifier of this very element. For example it can be element of type”GtkGrid” with id“grid”.

<objectclass="GtkGrid"id="grid">
Enter fullscreen modeExit fullscreen mode

As you remember we told Builder that our GUI stored insidegrid.ui file

letglade_src=include_str!("grid.ui");letbuilder=Builder::from_string(glade_src);
Enter fullscreen modeExit fullscreen mode

Nowgtk::Builder can find element by it’s id inside the markdown file and bind function to it. And we gonna do it right now.
Let’s createbutton0 and call it by id“button0”.

letbutton0:Button=builder.object("button0").expect("Couldn't get button0");
Enter fullscreen modeExit fullscreen mode

Now we can add function to it by callingconnect_clicked().

button0.connect_clicked(glib::clone!(@weakgrid=>move|button|{letleft_attach=grid.cell_left_attach(button);letnew_left_attach=ifleft_attach==2{0}else{left_attach+1};grid.set_cell_left_attach(button,new_left_attach);}));window.show_all();}
Enter fullscreen modeExit fullscreen mode

We are usingglib::clone! To make a clone and@weak or@strong(looks like both do the same in this case) to tell Rust what type of clone we want to use. Then we are moving type button into closure and returning it at the end with a new position in the grid.
Here we are declaring two variables:left_attach andnew_left_attach. After starting the application the first variable contains 1.
The value ofnew_left_attach depends on if else condition. Each click increasesleft_attach by 1. As soon as it getting 2, else condition makes it equal 0. Try to change conditions,

<propertyname="left_attach">1</property>
Enter fullscreen modeExit fullscreen mode

andbutton0 width.
Ok, now Button 0 slides right each time we click on it.

2. Numeric buttons

I told yougtk::Builder can bind function to element’s id but it also can send data. Let’s add numeric buttons and made them change their own labels.
Connect buttons using their id

letbutton1:Button=builder.object("button1").expect("Couldn't get button1");letbutton2:Button=builder.object("button2").expect("Couldn't get button2");letbutton3:Button=builder.object("button3").expect("Couldn't get button3");letbutton4:Button=builder.object("button4").expect("Couldn't get button4");letbutton5:Button=builder.object("button5").expect("Couldn't get button5");letbutton6:Button=builder.object("button6").expect("Couldn't get button6");letbutton7:Button=builder.object("button7").expect("Couldn't get button7");letbutton8:Button=builder.object("button8").expect("Couldn't get button8");letbutton9:Button=builder.object("button9").expect("Couldn't get button9");
Enter fullscreen modeExit fullscreen mode

Add some function that gonna set button’s label to numerical letter after button has been clicked on. Notice we don’t have to use curly braces in this case. Also we don’t needglib::clone! because we don’t move any data inside the closure. Methodset_label() sets new 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"));
Enter fullscreen modeExit fullscreen mode

Run and behold! Numeric buttons 1-9 change their labels after first click.

Screenshot №2

3. Quit button

Another dependency

useglib::clone;
Enter fullscreen modeExit fullscreen mode

In this case@weak and@strong are the same. Probably. Still not sure how it works.
Declarequit_button with type of Button. Pay attention atobject() name! Here we are calling object by it’s id. In previous chapter|button| was a type not a data. But now we needglib::clone! because we move window inside the closure to destroy it.

letquit_button:Button=builder.object("quit_button").expect("Couldn't get quit_button");quit_button.connect_clicked(clone!(@weakwindow=>move|_|unsafe{window.destroy()}));
Enter fullscreen modeExit fullscreen mode

Let’s compare it with no markdown file project from my previous post (do not add this code into our current project). As you can see, here we are creating button themselves.

letquit_button=gtk::Button::with_label("Quit");
Enter fullscreen modeExit fullscreen mode

Connecting function,which is completely the same as before…

quit_button.connect_clicked(clone!(@weakwindow=>move|_|unsafe{window.destroy()}));
Enter fullscreen modeExit fullscreen mode

and attaching it to grid.

grid.attach(&quit_button,3,1,1,4);
Enter fullscreen modeExit fullscreen mode

The difference is: 1) declaring 2) attaching

4. Updating label

Now make label receiving data by clicking button.
Hope you have noticed when we are using external markdown file, type annotations needed. We have to tell to compiler – this is a GTK object of type Label.

letcounter_label:gtk::Label=builder.object("GtkLabel_1").expect("Couldn't get GtkLabel_1");
Enter fullscreen modeExit fullscreen mode

Declaringminus_button andplus_button is the same as before, nothing special.

letminus_button:Button=builder.object("minus").expect("Couldn't get minus");letplus_button:Button=builder.object("plus").expect("Couldn't get plus");
Enter fullscreen modeExit fullscreen mode

Functions would be hundred percent the same as we made in pure GTK project.

plus_button.connect_clicked(glib::clone!(@weakcounter_label=>move|_|{letnb=counter_label.text().parse().unwrap_or(0.0);counter_label.set_text(&format!("{}",nb+1.1));}));minus_button.connect_clicked(glib::clone!(@weakcounter_label=>move|_|{letnb=counter_label.text().parse().unwrap_or(0.0);counter_label.set_text(&format!("{}",nb-1.2));}));
Enter fullscreen modeExit fullscreen mode

Now buttons work and send data to label.

Screenshot №3

5. Timer

Bring a new path into scope.

usechrono::Local;
Enter fullscreen modeExit fullscreen mode

At the end list of paths looks like this.

usegtk::glib;useglib::clone;usegtk::prelude::*;usegtk::{ApplicationWindow,Builder};usegtk::{Button,Grid};usechrono::Local;
Enter fullscreen modeExit fullscreen mode

Declare time variable like we did it on a previous project.

lettime=format!("{}",Local::now().format("%Y-%m-%d %H:%M:%S"));
Enter fullscreen modeExit fullscreen mode

But this part is different!

letlabel_time:gtk::Label=builder.object("GtkLabel_2").expect("Couldn't get GtkLabel_2");label_time.set_text(&time);
Enter fullscreen modeExit fullscreen mode

Just compare with the similar part in previous project (do not add this code into our current project).

letlabel_time=gtk::Label::new(None);label_time.set_text(&time);grid.attach(&label_time,0,6,4,1);
Enter fullscreen modeExit fullscreen mode

This is the same as in the case with pure GTK grid.

lettick=move||{lettime=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);
Enter fullscreen modeExit fullscreen mode

Now cargo run it and you will see this. All the buttons work fine. Just one little detail left…

Screenshot №4

6. Entry doesn’t work!

As documented Entry should look like this, but unfortunately, I wasn’t able to get it to work

<objectclass="GtkEntry"><attributes><attributename="weight"value="PANGO_WEIGHT_BOLD"/><attributename="background"value="red"start="5"end="10"/></attributes></object>
Enter fullscreen modeExit fullscreen mode

I spend some time trying to solve this riddle. I'll probably come back to this issue later.

CONCLUSION

There are still a few things here that I don't quite understand yet. And also there are some ideas that I can't implement right now. For example a built-in display or a save file button. But I think I will gradually figure this out as well.
Hope this post was useful and educational
Take care

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Rust / Python enthusiast. Seeking my way in development. Love back-end and command line. Guitar and bass player, songwriter, artist
  • Location
    Tbilisi, Georgia
  • Education
    Self educated
  • Work
    Part-time backend developer
  • Joined

More fromAntonov Mike

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp