In this section, we have two objectives. Weβre going to implement:
- the state for our app. But we need questions. I mean you should get some questions.
- and wire the logic with the UI
State
Our state is a Quiz
. Luckily, weβve already implemented that. So letβs add that to our App
struct.
struct App {
quiz: quiz::Quiz
}
Youβll get an error: struct 'Quiz' is private
. What we need to do is add pub
to the Quiz
struct in quiz.rs
:
pub struct Quiz {
// ...
}
And weβll have to update our App::new
function:
impl App {
fn new() -> Self {
let quiz = quiz::Quiz::sample();
Self { quiz }
}
}
::sample()
is not implemented for quiz::Quiz
. Want to give it a try?
The following is an implementation of the sample
function:
impl Quiz {
pub fn sample() -> Self {
Self {
// Notice how we create a vector! This is a macro that
// helps us create a vector with less code. Read on macros π€·π½ββοΈ
questions: vec![
Question {
title: "Is the sky blue?".to_string(),
answer: true,
user_answer: None
},
Question {
title: "Is the grass green?".to_string(),
answer: true,
user_answer: None
},
Question {
title: "Is the sun yellow?".to_string(),
answer: false,
user_answer: None
},
],
current_index: 0
}
}
// ...
}
::sample()
here doesnβt accept a &self
unlike the other functions weβve seen. You can treat functions like this as static functions. In Typescript, this would look like:
class Quiz {
static sample() {
return new Quiz(/* ... */)
}
}
This same format (ie, static functions) is used for constructors. In Rust, constructors are just static functions that return a new instance of the struct. Conventionally, you can call your constructor new
.
impl Quiz {
fn new(questions: Vec<Question>) -> Self {
Self {
questions
}
}
}
Logic
Back to our App::update
function, letβs actually render the current question and other relevant information:
impl eframe::app::App for App {
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut eframe::epi::Frame<'_>) {
egui::CentralPanel::default().show(ctx, |ui| {
let current_index = self.quiz.current_index + 1;
let count = self.quiz.questions.len();
// welcome to string formatting in Rust
ui.label(format!("{current_index}/{count}"));
let current_question = self.quiz.current_question();
ui.label(¤t_question.title);
// ...
});
}
}
Adding this piece of code will introduce a number of errors in the format:
field `current_index` of `Quiz` is private: rust-analyzer (E0616)
Add pub
to these fields, methods or structs to make them accessible to the main
module.
struct Quiz {
current_index: usize;
pub current_index: usize;
// ...
}
Letβs move on to add the buttons:
impl eframe::app::App for App {
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut eframe::epi::Frame<'_>) {
egui::CentralPanel::default().show(ctx, |ui| {
// ...
ui.horizontal(|ui| {
// Conventionally, you would have attached a click handler
// to the button. But remember, this is an immediated mode UI.
// So we have to perform this check every frame.
if ui.button("True").clicked() {
self.quiz.answer(true);
}
if ui.button("False").clicked() {
self.quiz.answer(false);
}
});
// ...
});
}
}
Next, Previous?
Hereβs your exercise:
- Implement the next and previous buttons. The question number and text should update accordingly.
- On the same row as True/False buttons, show a label only if the user has answered the question. The label should say whether the user got the question right or wrong. βCorrectβ or βWrongβ is fine.
β οΈ Remember that your next button will only work when you answer the current question. So click True/False first.
Letβs rearrange our UI next.