Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Choice Settings to the Auto Splitting Runtime #745

Merged
merged 1 commit into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions capi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,21 +120,19 @@ fn with_vec<F, R>(f: F) -> R
where
F: FnOnce(&mut Vec<u8>) -> R,
{
OUTPUT_VEC.with(|output| {
let mut output = output.borrow_mut();
OUTPUT_VEC.with_borrow_mut(|output| {
output.clear();
f(&mut output)
f(output)
})
}

fn output_vec<F>(f: F) -> *const c_char
where
F: FnOnce(&mut Vec<u8>),
{
OUTPUT_VEC.with(|output| {
let mut output = output.borrow_mut();
OUTPUT_VEC.with_borrow_mut(|output| {
output.clear();
f(&mut output);
f(output);
output.push(0);
output.as_ptr() as *const c_char
})
Expand Down Expand Up @@ -207,5 +205,5 @@ pub extern "C" fn dealloc(ptr: *mut u8, cap: usize) {
/// current thread. The length excludes the nul-terminator.
#[no_mangle]
pub extern "C" fn get_buf_len() -> usize {
OUTPUT_VEC.with(|v| v.borrow().len() - 1)
OUTPUT_VEC.with_borrow(|v| v.len() - 1)
}
28 changes: 28 additions & 0 deletions crates/livesplit-auto-splitting/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,34 @@ extern "C" {
description_len: usize,
heading_level: u32,
);
/// Adds a new choice setting that the user can modify. This allows the user
/// to choose between various options. The key is used to store the setting
/// in the settings map and needs to be unique across all types of settings.
/// The description is what's shown to the user. The key of the default
/// option to show needs to be specified. The pointers need to point to
/// valid UTF-8 encoded text with the respective given length.
pub fn user_settings_add_choice(
key_ptr: *const u8,
key_len: usize,
description_ptr: *const u8,
description_len: usize,
default_option_key_ptr: *const u8,
default_option_key_len: usize,
);
/// Adds a new option to a choice setting. The key needs to match the key of
/// the choice setting that it's supposed to be added to. The option key is
/// used as the value to store when the user chooses this option. The
/// description is what's shown to the user. The pointers need to point to
/// valid UTF-8 encoded text with the respective given length. Returns
/// `true` if the option is at this point in time chosen by the user.
pub fn user_settings_add_choice_option(
key_ptr: *const u8,
key_len: usize,
option_key_ptr: *const u8,
option_key_len: usize,
option_description_ptr: *const u8,
option_description_len: usize,
) -> bool;
/// Adds a tooltip to a setting based on its key. A tooltip is useful for
/// explaining the purpose of a setting to the user. The pointers need to
/// point to valid UTF-8 encoded text with the respective given length.
Expand Down
28 changes: 28 additions & 0 deletions crates/livesplit-auto-splitting/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,34 @@
//! description_len: usize,
//! heading_level: u32,
//! );
//! /// Adds a new choice setting that the user can modify. This allows the user
//! /// to choose between various options. The key is used to store the setting
//! /// in the settings map and needs to be unique across all types of settings.
//! /// The description is what's shown to the user. The key of the default
//! /// option to show needs to be specified. The pointers need to point to
//! /// valid UTF-8 encoded text with the respective given length.
//! pub fn user_settings_add_choice(
//! key_ptr: *const u8,
//! key_len: usize,
//! description_ptr: *const u8,
//! description_len: usize,
//! default_option_key_ptr: *const u8,
//! default_option_key_len: usize,
//! );
//! /// Adds a new option to a choice setting. The key needs to match the key of
//! /// the choice setting that it's supposed to be added to. The option key is
//! /// used as the value to store when the user chooses this option. The
//! /// description is what's shown to the user. The pointers need to point to
//! /// valid UTF-8 encoded text with the respective given length. Returns
//! /// `true` if the option is at this point in time chosen by the user.
//! pub fn user_settings_add_choice_option(
//! key_ptr: *const u8,
//! key_len: usize,
//! option_key_ptr: *const u8,
//! option_key_len: usize,
//! option_description_ptr: *const u8,
//! option_description_len: usize,
//! ) -> bool;
//! /// Adds a tooltip to a setting based on its key. A tooltip is useful for
//! /// explaining the purpose of a setting to the user. The pointers need to
//! /// point to valid UTF-8 encoded text with the respective given length.
Expand Down
66 changes: 65 additions & 1 deletion crates/livesplit-auto-splitting/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
timer::Timer,
};

use anyhow::{ensure, format_err, Context as _, Result};
use anyhow::{bail, ensure, format_err, Context as _, Result};
use slotmap::{Key, KeyData, SlotMap};
use snafu::Snafu;
use std::{
Expand Down Expand Up @@ -1127,6 +1127,70 @@ fn bind_interface<T: Timer>(linker: &mut Linker<Context<T>>) -> Result<(), Creat
source,
name: "user_settings_add_title",
})?
.func_wrap("env", "user_settings_add_choice", {
|mut caller: Caller<'_, Context<T>>,
key_ptr: u32,
key_len: u32,
description_ptr: u32,
description_len: u32,
default_option_key_ptr: u32,
default_option_key_len: u32| {
let (memory, context) = memory_and_context(&mut caller);
let key = get_str(memory, key_ptr, key_len)?.into();
let description = get_str(memory, description_ptr, description_len)?.into();
let default_option_key =
get_str(memory, default_option_key_ptr, default_option_key_len)?.into();
Arc::make_mut(&mut context.settings_widgets).push(settings::Widget {
key,
description,
tooltip: None,
kind: settings::WidgetKind::Choice {
default_option_key,
options: Arc::new(Vec::new()),
},
});
Ok(())
}
})
.map_err(|source| CreationError::LinkFunction {
source,
name: "user_settings_add_choice",
})?
.func_wrap("env", "user_settings_add_choice_option", {
|mut caller: Caller<'_, Context<T>>,
key_ptr: u32,
key_len: u32,
option_key_ptr: u32,
option_key_len: u32,
option_description_ptr: u32,
option_description_len: u32| {
let (memory, context) = memory_and_context(&mut caller);
let key = get_str(memory, key_ptr, key_len)?.into();
let option_key = get_str(memory, option_key_ptr, option_key_len)?.into();
let option_description =
get_str(memory, option_description_ptr, option_description_len)?.into();
let setting = Arc::make_mut(&mut context.settings_widgets)
.iter_mut()
.find(|s| s.key == key)
.context("There is no setting with the provided key.")?;
let (options, is_chosen) = match &mut setting.kind {
settings::WidgetKind::Choice {
options,
default_option_key,
} => (options, *default_option_key == option_key),
_ => bail!("The setting is not a choice."),
};
Arc::make_mut(options).push(settings::ChoiceOption {
key: option_key,
description: option_description,
});
Ok(is_chosen as u32)
}
})
.map_err(|source| CreationError::LinkFunction {
source,
name: "user_settings_add_choice_option",
})?
.func_wrap("env", "user_settings_set_tooltip", {
|mut caller: Caller<'_, Context<T>>,
key_ptr: u32,
Expand Down
20 changes: 20 additions & 0 deletions crates/livesplit-auto-splitting/src/settings/gui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,24 @@ pub enum WidgetKind {
/// settings [`Map`](super::Map) yet.
default_value: bool,
},
/// A choice setting. This could be shown as a dropdown or radio buttons.
Choice {
/// The default value of the setting, if it's not available in the
/// settings [`Map`](super::Map) yet.
default_option_key: Arc<str>,
/// The available options for the setting.
options: Arc<Vec<ChoiceOption>>,
},
}

/// An option for a choice setting.
#[derive(Clone)]
pub struct ChoiceOption {
/// The unique identifier of the option. This is not meant to be shown to
/// the user and is only used to keep track of the option. This key is used
/// to store and retrieve the value of the option from the main settings
/// [`Map`](super::Map).
pub key: Arc<str>,
/// The name of the option that is shown to the user.
pub description: Arc<str>,
}
3 changes: 1 addition & 2 deletions crates/livesplit-hotkey/src/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,7 @@ const fn parse_scan_code(value: u32) -> Option<KeyCode> {
}

unsafe extern "system" fn callback_proc(code: i32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
let hook = STATE.with(|state| {
let mut state = state.borrow_mut();
let hook = STATE.with_borrow_mut(|state| {
let state = state.as_mut().expect("State should be initialized by now");

if code >= 0 {
Expand Down
39 changes: 36 additions & 3 deletions src/auto_splitting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,34 @@
//! description_len: usize,
//! heading_level: u32,
//! );
//! /// Adds a new choice setting that the user can modify. This allows the user
//! /// to choose between various options. The key is used to store the setting
//! /// in the settings map and needs to be unique across all types of settings.
//! /// The description is what's shown to the user. The key of the default
//! /// option to show needs to be specified. The pointers need to point to
//! /// valid UTF-8 encoded text with the respective given length.
//! pub fn user_settings_add_choice(
//! key_ptr: *const u8,
//! key_len: usize,
//! description_ptr: *const u8,
//! description_len: usize,
//! default_option_key_ptr: *const u8,
//! default_option_key_len: usize,
//! );
//! /// Adds a new option to a choice setting. The key needs to match the key of
//! /// the choice setting that it's supposed to be added to. The option key is
//! /// used as the value to store when the user chooses this option. The
//! /// description is what's shown to the user. The pointers need to point to
//! /// valid UTF-8 encoded text with the respective given length. Returns
//! /// `true` if the option is at this point in time chosen by the user.
//! pub fn user_settings_add_choice_option(
//! key_ptr: *const u8,
//! key_len: usize,
//! option_key_ptr: *const u8,
//! option_key_len: usize,
//! option_description_ptr: *const u8,
//! option_description_len: usize,
//! ) -> bool;
//! /// Adds a tooltip to a setting based on its key. A tooltip is useful for
//! /// explaining the purpose of a setting to the user. The pointers need to
//! /// point to valid UTF-8 encoded text with the respective given length.
Expand Down Expand Up @@ -882,11 +910,16 @@ async fn run(
} else {
let widget_value =
match runtime.settings_widgets().iter().find(|x| *x.key == key) {
Some(widget) => match widget.kind {
Some(widget) => match &widget.kind {
settings::WidgetKind::Bool { default_value } => {
Some(settings::Value::Bool(default_value))
Some(settings::Value::Bool(*default_value))
}
settings::WidgetKind::Title { heading_level: _ } => None,
settings::WidgetKind::Title { .. } => None,
settings::WidgetKind::Choice {
default_option_key, ..
} => Some(settings::Value::String(
default_option_key.clone(),
)),
},
None => None,
};
Expand Down
2 changes: 1 addition & 1 deletion tests/rendering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ fn font_fallback() {
let build_number: u64 = system.kernel_version().unwrap().parse().unwrap();
let expected_hash = if build_number >= 22000 {
// Windows 11
"d16b447322881767"
"04fd5c64e5ca85f5"
} else {
// Windows 10
"f4bffc6bc6fab953"
Expand Down
Loading