Setting up Application Root
This guide will teach you how to architect the project of an mctk application.
File Structure
.
├── Cargo.lock
├── Cargo.toml
└── src
├── assets
│ ├── fonts
│ └── icons
├── components
│ └── mod.rs
├── constants.rs
├── contexts
│ └── mod.rs
├── gui.rs
├── main.rs
└── pages
└── homepage
└── mod.rs
- The
src/assets
directory holds all the assets of the application, including fonts, icons, and others. main.rs
is responsible for initializing static assets, opening the window, and managing contexts.gui.rs
creates theRootComponent<T>
, which also handles global messages and routing of the application.- The
contexts
directory contains the contexts of the application in a module. - The
components
directory contains the components of the application in a module. - The
pages
directory contains the pages of the application in a module. Each submodule of a page has a component that represents the page.
Structuring main.rs
The main.rs
will handle three major aspects:
#[tokio::main]
async fn main() -> anyhow::Result<()> {
...
}
Static Assets
Static assets include images, fonts, and icons. You pass these three to the WindowParameters
when opening the window.
Fonts are stored as the type cosmic_text::fontdb::Database
. Add fonts using the following code:
// Initialize the font database.
let mut fonts = cosmic_text::fontdb::Database::new();
// Load all system fonts.
fonts.load_system_fonts();
// Individually add custom fonts for your application.
fonts.load_font_data(include_bytes!("assets/fonts/<your-font-name>.ttf").into());
Icons and images are stored as the type HashMap<String, AssetParams>
. Add them as shown below:
// Initialize and add images.
let mut assets: HashMap<String, AssetParams> = HashMap::new();
assets.insert(
"<image_name>".to_string(),
AssetParams::new("src/assets/icons/<image>.png".to_string()),
);
// Initialize and add icons.
let mut svgs = HashMap::new();
svgs.insert(
"<icon_name>".to_string(),
"src/assets/icons/<icon>.svg".to_string(),
);
When using images or icons, use the key used to store them in the HashMap. For example, in IconButton
, use IconButton::new("<icon_name>")
or IconButton::new("<image_name>")
.
Opening the Window
This example demonstrates a LayerShell
application.
Initialize three variables to open a LayerShell
application:
let layer_shell_opts = LayerOptions {
...
};
let window_info = WindowInfo {
id: "mctk.guide.application".to_string(),
title: "Mctk Window".to_string(),
namespace: "mctk.guides.namespace",
};
let window_opts = WindowOptions {
height: 480,
width: 480,
scale_factor: 1.0,
};
Pass the configuration variables and static assets when opening the window. AppParams
are the parameters of your RootComponent
, which will be created in gui.rs
.
let (mut app, mut event_loop, ..) =
layer_window::LayerWindow::open_blocking::<Kitchen, KitchenParams>(
LayerWindowParams {
// Window Options
window_info,
window_opts,
layer_shell_opts,
// Static Assets
fonts,
assets,
svgs,
// Others
..Default::default()
},
AppParams {},
);
Finally, start the event loop:
loop {
event_loop
.dispatch(Duration::from_millis(16), &mut app)
.unwrap();
}
Managing Contexts
let window_tx_channel = window_tx.clone();
let context_handler = context::get_static_context_handler();
// This callback function will request a redraw to mctk
context_handler.register_on_change(Box::new(move || {
window_tx_channel
.send(WindowMessage::Send { message: msg!(0) })
.unwrap();
}));
// Register the callback function for all the contexts in the model.
AppModel::get().register_context_handler(context_handler);
Structuring gui.rs
The gui.rs
will contain our RootComponent
.
#[derive(Default, Debug)]
struct App;
#[derive(Debug, Clone)]
struct AppParams;
impl Component for App {}
impl RootComponent<AppParams> for App {}
This root component will be responsible for routing. Define the routes of your application in an enum:
#[derive(Debug, Copy, Clone, Hash)]
enum Route {
HomePage,
About
}
Routes will be changed using messages:
#[derive(Debug, Clone)]
pub enum Message {
ChangeRoute { route: Route },
}
This will be handled in the fn update(&mut self, messages: component::Message) -> Vec<component::Message>
of Component
:
impl Component for App {
...
fn update(&mut self, msg: component::Message) -> Vec<component::Message> {
if let Some(message) = msg.downcast_ref::<Message>() {
match message {
Message::ChangeRoute { route } => {
// Do something
}
}
}
vec![]
}
}
Store the current route in the AppState
. Modify the implementation slightly for that:
#[derive(Debug)]
pub struct AppState {
current_route: Route,
}
#[component(State = "AppState")] // This will add the AppState to our root component.
#[derive(Debug, Default)]
pub struct App {}
#[state_component_impl(AppState)] // This will add the state_mut, state_ref, and other functions to the implementation.
impl Component for App {
...
}
Display the routes in the fn view(&self) -> Option<Node>
for Component
:
impl Component for App {
...
fn view(&self) -> Option<Node> {
let current_route = self.state_ref().current_route;
let screen = match current_route {
Route::HomePage => node!(pages::HomePage {}),
Route::About => node!(pages::About {})
};
Some(screen)
}
}
To change routes, for example, on a click:
Button::new().on_click(Box::new(|| msg!(
Message::ChangeRoute { route: Route::About }
)));