Hello OrbTk!
We already introduced the source in Chapter 01.
For the sake of completeness, and since its a habit to begin with here we go.
Your first first experience with an OrbTk
app:
The project root
Change to your project root directory. If you didn’t already create the app in the first place, go ahead and type the following in your console:
$ cd ~/orbtk-book/examples
Create the source code
Next we will use cargo to create the app. All boilerplate tasks are handled using cargo’s inherited template handling.
$ cargo new orbtk_hello
$ cd orbtk_hello
The first command, cargo new
, takes the name of the project
(“orbtk_hello
”) as the first argument. The second command changes to
the new project’s directory.
Look at the generated Cargo.toml file:
Filename: Cargo.toml
[package]
name = "orbtk_hello_example"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
With cargo new
, a default project structure is created. Maybe the
author information is already exchanged if Cargo could obtain a definition
from your environment. Cargo also generated source code for a “Hello, world!”
program. Let’s Check out the corresponding src/main.rs file:
Filename: src/main.rs
fn main() { println!("Hello, world!"); }
No need to compile that stage with cargo run
, since we are going to
exchange the project metadata, as well as the orbtk source code right
away.
Update Cargo.toml
First reopen the Cargo.toml file and enter the Code in Listing 1-1 into Cargo.toml
Filename: Cargo.toml
[package]
name = "orbtk_hello"
version = "0.3.1-alpha4"
authors = [
"Florian Blasius <flovanpt@posteo.de>",
"Ralf Zerres <ralf.zerres.de@gmail.com>",
]
description = "The Orbital Widget Toolkit - Training project"
documentation = "https://docs.rs/orbtk"
repository = "https://github.com/redox-os/orbtk"
readme = "README.md"
license = "MIT"
keywords = [
"orbital",
"widget",
"ui",
]
edition = "2018"
[profile.dev]
opt-level = 1
[dependencies]
orbtk = { git = "https://github.com/redox-os/orbtk.git", branch = "develop" }
#orbtk = { path = "../../../orbtk", branch="next" }
[[bin]]
name = "orbtk_hello"
path = "src/main.rs"
You may wonder, why the name property inside the Cargo.toml is
formatted like hello_orbtk
.
name = "orbtk_hello"
It is a good habit to follow rusts
naming convention, that encourages you to use snake_case
naming. While expanding the OrbTk example sources, we will keep
the grouping prefix orbtk
. That way we end up to call our first target
binary orbtk_hello
.
Update main.rs
All of the OrbTk specific code that is needed to build our first example “Hello OrbTk!” is shown in Listing 1-2. It goes to src/main.rs.
Filename: src/main.rs
use orbtk::prelude::*;
fn main() {
// use this only if you want to run it as web application.
orbtk::initialize();
Application::new()
.window(|ctx| {
Window::new()
.title("OrbTk-Book - Chapter 1.2")
.position((100.0, 100.0))
.size(420.0, 140.0)
.child(
TextBlock::new()
.font_size(28)
.h_align("center")
.text("Hey OrbTk!")
.v_align("center")
.build(ctx)
)
.build(ctx)
})
.run();
}
Save the file and go back to your terminal window. Enter the following commands to compile and run the file:
$ cargo run --release --example orbtk_hello
Regardless of your operating system, a window should be placed on the screen
that prints the string Hey OrbTk!
in its center.
If something is preventing to position the window, refer back to the
“Troubleshooting” part of the Installation section for ways to get help.
If you can see the rendered output of your Hey OrbTk!
app on screen,
congratulations! You’ve written your first OrbTk application.
That makes you an OrbTk programmer — welcome!
Recap and annotation
The anatomy of an OrbTk application
Let’s review in detail what just happened in your “Hey OrbTk!” application. Here’s the first piece of the puzzle:
use orbtk::prelude::*;
fn main() {
// use this only if you want to run it as web application.
orbtk::initialize();
Application::new()
.window(|ctx| {
Window::new()
.title("OrbTk-Book - Chapter 1.2")
.position((100.0, 100.0))
.size(420.0, 140.0)
.child(
TextBlock::new()
.font_size(28)
.h_align("center")
.text("Hey OrbTk!")
.v_align("center")
.build(ctx)
)
.build(ctx)
})
.run();
}
The first line is introducing a use declaration. A use declaration is used to shorten the path required to refer to rust module items. The prelude is a convenient way to a list of things, that rust will automatically import to you program. Here, we bind the path orbtk::prelude. All default items defined in this path (referenced with ::) are now accessible in your source using their shorthand name. No need to type in their common prefix (orbtk::prelude::)
use orbtk::prelude::*;
fn main() {
// use this only if you want to run it as web application.
orbtk::initialize();
Application::new()
.window(|ctx| {
Window::new()
.title("OrbTk-Book - Chapter 1.2")
.position((100.0, 100.0))
.size(420.0, 140.0)
.child(
TextBlock::new()
.font_size(28)
.h_align("center")
.text("Hey OrbTk!")
.v_align("center")
.build(ctx)
)
.build(ctx)
})
.run();
}
the third line define a function in Rust. The main
function is special: it is
always the first code that runs in every executable Rust program. The first
line declares a function named main
that has no parameters and returns
nothing. If there were parameters, they would go inside the parentheses, ()
.
Also, note that the function body is wrapped in curly brackets, {}
. Rust
requires these around all function bodies. It’s good style to place the opening
curly bracket on the same line as the function declaration, adding one space in
between.
An automatic formatter tool called rustfmt
will help you to stick to a
standard style across Rust projects. OrbTk is following this guidance.
rustfmt
will format your code in a particular style. Depending on the version
of your rust toolchain, it is probably already installed on your computer!
Check the online documentation for more details.
Inside the main
function is the following code:
use orbtk::prelude::*;
fn main() {
// use this only if you want to run it as web application.
orbtk::initialize();
Application::new()
.window(|ctx| {
Window::new()
.title("OrbTk-Book - Chapter 1.2")
.position((100.0, 100.0))
.size(420.0, 140.0)
.child(
TextBlock::new()
.font_size(28)
.h_align("center")
.text("Hey OrbTk!")
.v_align("center")
.build(ctx)
)
.build(ctx)
})
.run();
}
Here are some important details to notice.
- First, Rust style is to indent with four spaces, not a tab.
- Second, the method
orbkt::initialize
does all the hard work to initialize the orbtk environment.
use orbtk::prelude::*;
fn main() {
// use this only if you want to run it as web application.
orbtk::initialize();
Application::new()
.window(|ctx| {
Window::new()
.title("OrbTk-Book - Chapter 1.2")
.position((100.0, 100.0))
.size(420.0, 140.0)
.child(
TextBlock::new()
.font_size(28)
.h_align("center")
.text("Hey OrbTk!")
.v_align("center")
.build(ctx)
)
.build(ctx)
})
.run();
}
- Third, the method
Application::new
creates a new entity in the entity component system (DECS). DECS is an OrbTk dependency that will create and organize all OrbTk entities. If OrbTk methods change attributes to the widget elements, the corresponding DECS object will store this attributes as components to the given entity.
We’ll discuss OrbTk macros and methods in more detail in Chapter <WIP: chapter>.
For now, you just need to know that using a ::new()
means that you’re calling
the creation method of a given widget (here: Application
).
Let’s explain the next lines:
use orbtk::prelude::*;
fn main() {
// use this only if you want to run it as web application.
orbtk::initialize();
Application::new()
.window(|ctx| {
Window::new()
.title("OrbTk-Book - Chapter 1.2")
.position((100.0, 100.0))
.size(420.0, 140.0)
.child(
TextBlock::new()
.font_size(28)
.h_align("center")
.text("Hey OrbTk!")
.v_align("center")
.build(ctx)
)
.build(ctx)
})
.run();
}
Inside the Application
method, we pipe in further instructions. Please notice
the important details:
- First, Rust style is to indent with another four spaces, not a tab.
- Second, The piping is encoded using a
dot
followed by a new method name (herewindow
). - Third, the
windows
method takes a Rust closure as its argument.
If you are not familiar with the concept of
closures,
go ahead and consult the Rust book reference for a deep dive. For now,
you just need to know that a closure can be used as a language
shortcut for a function. When the closure |ctx| {}
is executed, the
result will be captured inside a return variable (ctx
). The curly
braces define the body, with the code that is executed inside the
closure.
Let’s examine this body code of our closure:
- First, we call a method to create a new window entity.
(
Windows::new
). - Second, we define attributes attached to this entity (
title
,position
,size
). - Third, inside the defined windows, we create a new child entity
(
child
).
use orbtk::prelude::*;
fn main() {
// use this only if you want to run it as web application.
orbtk::initialize();
Application::new()
.window(|ctx| {
Window::new()
.title("OrbTk-Book - Chapter 1.2")
.position((100.0, 100.0))
.size(420.0, 140.0)
.child(
TextBlock::new()
.font_size(28)
.h_align("center")
.text("Hey OrbTk!")
.v_align("center")
.build(ctx)
)
.build(ctx)
})
.run();
}
- Forth, the child method takes arguments. We create a new text block
entity (
Textblock::new
). The text block is extended with the attributes (text
,h_align
,v_align
). The text attribute takes the desired string. Its positioning is controlled with the attribution of the horizontal and vertical alignment. By choosing “center”, we do advise the renderer to place the entity centered within its parent entity, which is the window.
use orbtk::prelude::*;
fn main() {
// use this only if you want to run it as web application.
orbtk::initialize();
Application::new()
.window(|ctx| {
Window::new()
.title("OrbTk-Book - Chapter 1.2")
.position((100.0, 100.0))
.size(420.0, 140.0)
.child(
TextBlock::new()
.font_size(28)
.h_align("center")
.text("Hey OrbTk!")
.v_align("center")
.build(ctx)
)
.build(ctx)
})
.run();
}
OrbTk is as lazy as possible. We need to call the build method (build(ctx)
),
that will instantiate our methods and let the renderer do its work.
use orbtk::prelude::*;
fn main() {
// use this only if you want to run it as web application.
orbtk::initialize();
Application::new()
.window(|ctx| {
Window::new()
.title("OrbTk-Book - Chapter 1.2")
.position((100.0, 100.0))
.size(420.0, 140.0)
.child(
TextBlock::new()
.font_size(28)
.h_align("center")
.text("Hey OrbTk!")
.v_align("center")
.build(ctx)
)
.build(ctx)
})
.run();
}
With the last statement, we finally call the method that will activate the
Application and draw the Widget on our screen (run
).
Most lines of Rust code are finalized with a semicolon (;
), to indicates that this
expression is finished and the next one is ready to begin.
Compiling and Running Are Separate Steps
Before running an OrbTk application, you must compile its source code. A typical OrbTk project will generate the executable binary code using cargo and place the result in the target subfolder of the project.
Profiles may be used to configure compiler options such as optimization levels
and debug settings. By default the dev
or test
profiles are used. If the
--release
flag is given, then the release or bench profiles are used.
$ cargo build --release --bin orbtk_hello.rs
$ ../target/release/hello_orbtk
On Windows, you need to use backslash
as a path delimiter:
> cargo build --release --bin orbtk-hello.rs
> ..\target\release\orbtk_hello.exe
If you like to get debug feedback you can call the build process like this
$ cargo build --features debug --bin hello_orbtk.rs