In the previous step, we created a new empty Cargo package. In this step, we're going to turn this empty package into a minimal Makepad app consisting of an empty window. ## What you will learn In this step, you will learn: - How to add Makepad as a dependency. - How to use DSL code to define the layout and styling of your app. - How to create an empty window. > **Note:** > If you don't feel like typing along, you can find all the code for this step here: > https://github.com/makepad/image_viewer/tree/main/step_2 ## Adding Makepad as a Dependency We'll start by adding Makepad as a dependency to our project. The top-level crate for Makepad is called **makepad-widgets**. It contains everything we need to build an app. To add **makepad-widgets** as a dependency, run: ``` cargo add makepad-widgets ``` ## Adding the Code Next, we'll add the code we need. Copy the following files to the **src** directory (we'll explain what the code does shortly): **app.rs:** ```rust use makepad_widgets::*; live_design! { use link::widgets::*; App = {{App}} { ui: <Root> { <Window> { body = <View> {} } } } } #[derive(Live, LiveHook)] pub struct App { #[live] ui: WidgetRef, } impl AppMain for App { fn handle_event(&mut self, cx: &mut Cx, event: &Event) { self.ui.handle_event(cx, event, &mut Scope::empty()); } } impl LiveRegister for App { fn live_register(cx: &mut Cx) { makepad_widgets::live_design(cx); } } app_main!(App); ``` **lib.rs:** ```rust pub mod app; ``` **main.rs:** ```rust fn main() { ::app::app_main() } ``` ## Checking our progress so far Let's check our progress so far. Make sure you're in your package directory, and run: ``` cargo run --release ``` If everything is working correctly, an empty window should appear on your screen: ![[Empty Window.png]] ## What the Code does ### Live Design Blocks The following code defines a **live design block**: ```rust live_design! { use link::widgets::*; App = {{App}} { ui: <Root> { <Window> { body = <View> {} } } } } ``` A live design block is used to define Makepad **DSL code**. DSL code is used in Makepad to define the layout and styling of our app, similar to CSS. Let's break it down: - `use link::widgets::*;` imports built-in widgets, such as `Root`, `Window`, and `View`. - `App = {{App}} { ... }` defines an `App`. - The `{{App}}` syntax links our definition of `App` to an `App` struct in the Rust code (more on this later). - Within `App`, we define the `ui` field as the root of our widget tree: - `<Root> { ... }` is our top-level container. - `<Window> { ... }` is a window on the screen. - `body = <View> {}` is an empty view named `body`. #### The Live Design System When we run our app, Makepad uses something called the **live design system** to bridge DSL code with Rust code. The idea is this: - The layout and styling of our app is defined in DSL code. - The live design system matches DSL definitions to their corresponding Rust structs, and initialises these structs with the values from the DSL. - If the DSL code is later changed in Makepad Studio the corresponding Rust structs are automatically updated at runtime, without having to restart our app. The live design system is what enables Makepad’s powerful runtime styling capabilities. You will be able to use Makepad Studio to tweak the layout, colors, and other styling properties of the UI and instantly see the results in your app while it runs — no recompilation required! ### The `App` Struct The following code defines the `App` struct: ```rust #[derive(Live, LiveHook)] pub struct App { #[live] ui: WidgetRef, } ``` The `App` struct serves as the core of our app. For now, it just contains the root of our widget tree. Later on, we will also add any state we need for our app. Note that we are deriving two traits for the `App` struct: `Live` and `LiveHook`. Let's take a closer look at those. #### The `Live` Trait The `Live` trait enables a struct to interact with the live design system. To derive the `Live` trait for a struct, each field on the struct needs to be marked with one of the following attributes: - The `#[live]` attribute marks the field as part of the live design system. - The `#[rust]` attribute marks it as an ordinary Rust field. When the live design system encounters a field marked with the `#[live]` attribute, it looks for a matching definition for that field in the DSL code. If it finds one, it will use that definition to instantiate the field. Otherwise, it instantiates the field with a default value. In contrast, when the live design systems encounters a field marked with the `#[rust]` attribute, it uses the `Default::default` constructor for values of that type to instantiate the field. In our case, we mark the `ui` field of the `App` struct with the `#[live]` attribute. Since we have a corresponding definition for this field in the DSL, the live design system automatically populates the `ui` field with the widget tree as we defined in the DSL: a `Root` widget containing a single `Window` with an empty `View`. #### The `LiveHook` Trait The `LiveHook` trait provides several overridable methods that will be called at various points during our app's lifetime. We don't need any of these methods right now, so we could just define an empty implementation of the `LiveHook` for the `App` struct: ```Rust impl LiveHook for App {} ``` Alternatively, we can derive the `LiveHook` trait for the `App` struct, as we have done here. This will generate the same implementation as above, which saves us some typing. We'll explain the `LiveHook` trait in more detail once we actually do mneed some of these methods. ### The `AppMain` Trait The following code implements the `AppMain` trait for the `App` struct: ```rust impl AppMain for App { fn handle_event(&mut self, cx: &mut Cx, event: &Event) { self.ui.handle_event(cx, event, &mut Scope::empty()); } } ``` The `AppMain` trait is used to hook an instance of the `App` struct into the main event loop. In our implementation, we simply forward all events to the root of our widget tree, so it can handle them appropriately. A **scope** in Makepad is a container that is used to pass both app-wide data and widget-specific props along with each event. We don't have any state yet, so we just call `Scope::empty()` for now to create an empty scope for each event. We'll have more to say about scopes later. ### The `LiveRegister` Trait The following code implements the `LiveRegister` trait for the `App` struct: ```rust impl LiveRegister for App { fn live_register(cx: &mut Cx) { makepad_widgets::live_design(cx); } } ``` The `LiveRegister` trait is used to register DSL code. By registering DSL code, we make it available to the rest of our app. Earlier, we said that live design blocks are used to define DSL code. Under the hood, each live design block generates a `live_design` function which, when called, registers the DSL code in the block. The `LiveRegister` trait is where we call these `live_design` functions. In our implementation, we call the `makepad_widgets::live_design` function to register the DSL code in the **makepad_widgets** crate. Without this call, we would not be able to use any of the built-in widgets such as `Root`, `Window`, and `View` that we saw earlier. >**Note:** >The `live_design` function generated by the `live_design` macro at the top of `app.rs` is special. We don't need to call it here, because it is automatically called by the `app_main` function. This function is generated by the `app_main` macro, which we'll explain next. ### The `app_main` macro The following code calls the `app_main` macro: ```rust app_main!(App); ``` The `app_main` macro generates an `app_main` function that contains all the code necessary to start our app. Here's what the `app_main` function does: - It uses the `LiveRegister` trait to register additional DSL code. - It registers the DSL code at the top of the file. - It uses the live design system to instantiate the `App` struct. - It uses the `AppMain` trait to hook this instance of the `App` struct into the main event loop. ## What's next We now have a minimal Makepad app consisting of an empty window. In the next few steps, we'll start building an image grid for our app.