Item 2 (Grok round 4): isboxer-style keybind tree UI.
Master: "The configurations for keybinds and video fx cannot be a preconfigured page like you have done. I need to have the ability to configure as many or few as I want, the way isboxer solved this was a tree where I can endless add items, when I click an item I would be able to configure that one item." References: - https://wiki.isboxer.com/Key_mapping - https://wiki.isboxer.com/Key_broadcasting Implementation: src/profile.rs: * Map.category: String field added (default "Mapped Keys" via a serde default helper). Categories: "Always On", "Combat", "Mapped Keys", "Character Sets", "Characters". * VideoFx.category: String field added (default "Video FX" via a serde default helper). * default_map_category + default_vfx_category helpers added. src/gui.rs: * SelectedNode enum: Map(usize) | VideoFx(usize). Drives the right-pane detail editor. * App struct gets a `selected_node: Option<SelectedNode>` field. * page_macros is gone. page_keybinds replaces it. * keybind_tree (left pane): CollapsingHeader per category. Items listed as selectable_label rows with a Remove button. "Add to <category>" button creates a new Map (or VideoFx for the VFX branch) with sane defaults. * keybind_detail (right pane): when a node is selected, shows that nodes existing fields -- for a Map: category dropdown, name, hotkey, hold checkbox, and the existing steps editor with an Add step button. For a VideoFx: the existing on / name / slot / pass_through / source / viewer / fps editors. * Page::Macros dispatch renamed from page_macros to page_keybinds. * ComboBox::from_id_source replaced with ComboBox::from_label (clippy::deprecated fix). DragValue uses .range (not the deprecated .clamp_range). Dropped unused crate::macros::print_macros import. format!("{}", x) replaced with x.to_string(). * All existing Map + VideoFx literal construction sites in engine.rs + gui.rs got the new category field injected with syntactically correct commas. cargo test 103/103; clippy clean.
This commit is contained in:
parent
8cb96c1f25
commit
24ca3bac0c
@ -311,6 +311,7 @@ mod tests {
|
|||||||
maps: vec![
|
maps: vec![
|
||||||
Map {
|
Map {
|
||||||
name: "bar1".into(),
|
name: "bar1".into(),
|
||||||
|
category: "Mapped Keys".to_string(),
|
||||||
hotkey: Hotkey("1".into()),
|
hotkey: Hotkey("1".into()),
|
||||||
hold: false,
|
hold: false,
|
||||||
steps: vec![
|
steps: vec![
|
||||||
@ -330,7 +331,8 @@ mod tests {
|
|||||||
release_steps: vec![],
|
release_steps: vec![],
|
||||||
},
|
},
|
||||||
Map {
|
Map {
|
||||||
// Smart shortcut: one user keypress fires the entire interact
|
// Smart shortcut: one user keypress fires the entire interact,
|
||||||
|
category: "Mapped Keys".to_string(),
|
||||||
// chain (CTM on -> Alt+J -> sleep walk_delay_ms -> CTM off).
|
// chain (CTM on -> Alt+J -> sleep walk_delay_ms -> CTM off).
|
||||||
name: "loot".into(),
|
name: "loot".into(),
|
||||||
hotkey: Hotkey("Alt+G".into()),
|
hotkey: Hotkey("Alt+G".into()),
|
||||||
@ -356,6 +358,7 @@ mod tests {
|
|||||||
// between, conditional branches, etc.).
|
// between, conditional branches, etc.).
|
||||||
Map {
|
Map {
|
||||||
name: "loot_manual".into(),
|
name: "loot_manual".into(),
|
||||||
|
category: "Mapped Keys".to_string(),
|
||||||
hotkey: Hotkey("Ctrl+Alt+G".into()),
|
hotkey: Hotkey("Ctrl+Alt+G".into()),
|
||||||
hold: false,
|
hold: false,
|
||||||
steps: vec![
|
steps: vec![
|
||||||
@ -466,6 +469,7 @@ mod tests {
|
|||||||
let mut e = sample();
|
let mut e = sample();
|
||||||
let map = crate::profile::Map {
|
let map = crate::profile::Map {
|
||||||
name: "interact_simple".into(),
|
name: "interact_simple".into(),
|
||||||
|
category: "Mapped Keys".to_string(),
|
||||||
hotkey: Hotkey("Alt+U".into()),
|
hotkey: Hotkey("Alt+U".into()),
|
||||||
hold: false,
|
hold: false,
|
||||||
steps: vec![crate::profile::Step {
|
steps: vec![crate::profile::Step {
|
||||||
@ -498,6 +502,7 @@ mod tests {
|
|||||||
e.profile.interact.walk_delay_ms = 2500;
|
e.profile.interact.walk_delay_ms = 2500;
|
||||||
let map = crate::profile::Map {
|
let map = crate::profile::Map {
|
||||||
name: "interact_short".into(),
|
name: "interact_short".into(),
|
||||||
|
category: "Mapped Keys".to_string(),
|
||||||
hotkey: Hotkey("Alt+I".into()),
|
hotkey: Hotkey("Alt+I".into()),
|
||||||
hold: false,
|
hold: false,
|
||||||
steps: vec![crate::profile::Step {
|
steps: vec![crate::profile::Step {
|
||||||
@ -535,6 +540,7 @@ mod tests {
|
|||||||
e.profile.interact.walk_delay_ms = 9999;
|
e.profile.interact.walk_delay_ms = 9999;
|
||||||
let map = crate::profile::Map {
|
let map = crate::profile::Map {
|
||||||
name: "interact_auto".into(),
|
name: "interact_auto".into(),
|
||||||
|
category: "Mapped Keys".to_string(),
|
||||||
hotkey: Hotkey("Alt+I".into()),
|
hotkey: Hotkey("Alt+I".into()),
|
||||||
hold: false,
|
hold: false,
|
||||||
steps: vec![crate::profile::Step {
|
steps: vec![crate::profile::Step {
|
||||||
@ -565,6 +571,7 @@ mod tests {
|
|||||||
e.profile.interact.walk_delay_ms = 5000;
|
e.profile.interact.walk_delay_ms = 5000;
|
||||||
let map = crate::profile::Map {
|
let map = crate::profile::Map {
|
||||||
name: "interact_hold".into(),
|
name: "interact_hold".into(),
|
||||||
|
category: "Mapped Keys".to_string(),
|
||||||
hotkey: Hotkey("Alt+I".into()),
|
hotkey: Hotkey("Alt+I".into()),
|
||||||
hold: true,
|
hold: true,
|
||||||
steps: vec![crate::profile::Step {
|
steps: vec![crate::profile::Step {
|
||||||
@ -701,6 +708,7 @@ mod tests {
|
|||||||
groups: BTreeMap::new(),
|
groups: BTreeMap::new(),
|
||||||
maps: vec![Map {
|
maps: vec![Map {
|
||||||
name: map_name.into(),
|
name: map_name.into(),
|
||||||
|
category: "Mapped Keys".to_string(),
|
||||||
hotkey: Hotkey("F8".into()),
|
hotkey: Hotkey("F8".into()),
|
||||||
hold: false,
|
hold: false,
|
||||||
steps: vec![Step {
|
steps: vec![Step {
|
||||||
|
|||||||
220
src/gui.rs
220
src/gui.rs
@ -1,7 +1,6 @@
|
|||||||
//! Control panel: menus for profile, routing mode, maps, and live crops.
|
//! Control panel: menus for profile, routing mode, maps, and live crops.
|
||||||
|
|
||||||
use crate::hotkey::Hotkey;
|
use crate::hotkey::Hotkey;
|
||||||
use crate::macros::print_macros;
|
|
||||||
use crate::profile::{
|
use crate::profile::{
|
||||||
Character, default_config_path, Group, LayoutMode, LayoutPreset, Map, Mode, NormRect, Profile,
|
Character, default_config_path, Group, LayoutMode, LayoutPreset, Map, Mode, NormRect, Profile,
|
||||||
Repeater, Step, VideoFx,
|
Repeater, Step, VideoFx,
|
||||||
@ -64,6 +63,7 @@ pub fn run() -> Result<()> {
|
|||||||
// Item 1: Free-mode buttons push IPC verbs here; the main
|
// Item 1: Free-mode buttons push IPC verbs here; the main
|
||||||
// loop drains the queue and writes them to the socket.
|
// loop drains the queue and writes them to the socket.
|
||||||
pending_ipc: Vec::new(),
|
pending_ipc: Vec::new(),
|
||||||
|
selected_node: None,
|
||||||
};
|
};
|
||||||
eframe::run_native("enBoxer", native, Box::new(|_cc| Ok(Box::new(app))))
|
eframe::run_native("enBoxer", native, Box::new(|_cc| Ok(Box::new(app))))
|
||||||
.map_err(|e| anyhow::anyhow!("{e}"))?;
|
.map_err(|e| anyhow::anyhow!("{e}"))?;
|
||||||
@ -96,6 +96,14 @@ fn default_profile() -> Profile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Item 2: which tree node the user has selected in the
|
||||||
|
/// page_keybinds tree (drives the right-pane detail editor).
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
enum SelectedNode {
|
||||||
|
Map(usize),
|
||||||
|
VideoFx(usize),
|
||||||
|
}
|
||||||
|
|
||||||
struct App {
|
struct App {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
profile: Profile,
|
profile: Profile,
|
||||||
@ -118,6 +126,8 @@ struct App {
|
|||||||
/// Item 1: Free-mode buttons push IPC verbs here; the main
|
/// Item 1: Free-mode buttons push IPC verbs here; the main
|
||||||
/// loop drains the queue and writes them to the socket.
|
/// loop drains the queue and writes them to the socket.
|
||||||
pending_ipc: Vec<String>,
|
pending_ipc: Vec<String>,
|
||||||
|
/// Item 2: which tree node is currently selected.
|
||||||
|
selected_node: Option<SelectedNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -563,6 +573,7 @@ impl eframe::App for App {
|
|||||||
if ui.button("Add crop").clicked() {
|
if ui.button("Add crop").clicked() {
|
||||||
self.profile.video_fx.push(VideoFx {
|
self.profile.video_fx.push(VideoFx {
|
||||||
name: format!("crop{}", self.profile.video_fx.len() + 1),
|
name: format!("crop{}", self.profile.video_fx.len() + 1),
|
||||||
|
category: "Video FX".to_string(),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
source_slot: 2,
|
source_slot: 2,
|
||||||
source: NormRect {
|
source: NormRect {
|
||||||
@ -635,7 +646,7 @@ impl eframe::App for App {
|
|||||||
Page::Layout => self.page_layout(ui),
|
Page::Layout => self.page_layout(ui),
|
||||||
Page::Maps => self.page_maps(ui),
|
Page::Maps => self.page_maps(ui),
|
||||||
Page::Video => self.page_video(ui),
|
Page::Video => self.page_video(ui),
|
||||||
Page::Macros => self.page_macros(ui),
|
Page::Macros => self.page_keybinds(ui),
|
||||||
Page::Teams => self.page_teams(ui),
|
Page::Teams => self.page_teams(ui),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -786,6 +797,7 @@ impl App {
|
|||||||
if ui.button("Add map").clicked() {
|
if ui.button("Add map").clicked() {
|
||||||
self.profile.maps.push(Map {
|
self.profile.maps.push(Map {
|
||||||
name: format!("map{}", self.profile.maps.len() + 1),
|
name: format!("map{}", self.profile.maps.len() + 1),
|
||||||
|
category: "Mapped Keys".to_string(),
|
||||||
hotkey: Hotkey("1".into()),
|
hotkey: Hotkey("1".into()),
|
||||||
hold: false,
|
hold: false,
|
||||||
steps: vec![Step {
|
steps: vec![Step {
|
||||||
@ -865,6 +877,7 @@ impl App {
|
|||||||
if ui.button("Add crop").clicked() {
|
if ui.button("Add crop").clicked() {
|
||||||
self.profile.video_fx.push(VideoFx {
|
self.profile.video_fx.push(VideoFx {
|
||||||
name: format!("crop{}", self.profile.video_fx.len() + 1),
|
name: format!("crop{}", self.profile.video_fx.len() + 1),
|
||||||
|
category: "Video FX".to_string(),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
source_slot: 2.min(self.profile.slots),
|
source_slot: 2.min(self.profile.slots),
|
||||||
source: NormRect {
|
source: NormRect {
|
||||||
@ -1304,13 +1317,204 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn page_macros(&mut self, ui: &mut egui::Ui) {
|
fn page_keybinds(&mut self, ui: &mut egui::Ui) {
|
||||||
ui.heading("In-game macros");
|
ui.heading("Key bindings + video FX");
|
||||||
ui.label("Bind these on every account. enBoxer only sends keystrokes.");
|
ui.label("Tree editor: click an item to edit it. Add as many or as few as you want per category.");
|
||||||
let text = print_macros(&self.profile);
|
ui.horizontal(|ui| {
|
||||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
// LEFT: tree
|
||||||
ui.monospace(text);
|
egui::SidePanel::left("tree")
|
||||||
|
.resizable(true)
|
||||||
|
.default_width(260.0)
|
||||||
|
.show_inside(ui, |ui| {
|
||||||
|
self.keybind_tree(ui);
|
||||||
});
|
});
|
||||||
|
// RIGHT: detail editor
|
||||||
|
egui::CentralPanel::default().show_inside(ui, |ui| {
|
||||||
|
self.keybind_detail(ui);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Item 2: tree (left pane). Categories as CollapsingHeaders,
|
||||||
|
/// items as selectable buttons. Add/Remove buttons per
|
||||||
|
/// category.
|
||||||
|
fn keybind_tree(&mut self, ui: &mut egui::Ui) {
|
||||||
|
let map_cats = ["Always On", "Combat", "Mapped Keys", "Character Sets", "Characters"];
|
||||||
|
for cat in map_cats {
|
||||||
|
let _header = egui::CollapsingHeader::new(cat)
|
||||||
|
.default_open(cat == "Mapped Keys")
|
||||||
|
.show(ui, |ui| {
|
||||||
|
// List items in this category.
|
||||||
|
let mut remove_idx = None;
|
||||||
|
for (i, m) in self.profile.maps.iter().enumerate() {
|
||||||
|
if m.category != cat {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let selected = self.selected_node == Some(SelectedNode::Map(i));
|
||||||
|
if ui.selectable_label(selected, format!("{} -> {}", m.name, m.hotkey.0)).clicked() {
|
||||||
|
self.selected_node = Some(SelectedNode::Map(i));
|
||||||
|
}
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.button("Remove").clicked() {
|
||||||
|
remove_idx = Some(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(idx) = remove_idx {
|
||||||
|
self.profile.maps.remove(idx);
|
||||||
|
// Re-resolve selected_node so we don't
|
||||||
|
// hold a stale index.
|
||||||
|
match self.selected_node {
|
||||||
|
Some(SelectedNode::Map(j)) if j == idx => self.selected_node = None,
|
||||||
|
Some(SelectedNode::Map(j)) if j > idx => {
|
||||||
|
self.selected_node = Some(SelectedNode::Map(j - 1));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ui.button(format!("+ Add to {}", cat)).clicked() {
|
||||||
|
let n = self.profile.maps.len();
|
||||||
|
self.profile.maps.push(crate::profile::Map {
|
||||||
|
category: cat.to_string(),
|
||||||
|
name: format!("new_map_{}", n + 1),
|
||||||
|
hotkey: crate::hotkey::Hotkey("".into()),
|
||||||
|
hold: false,
|
||||||
|
steps: Vec::new(),
|
||||||
|
release_steps: Vec::new(),
|
||||||
|
});
|
||||||
|
self.selected_node = Some(SelectedNode::Map(n));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Video FX branch.
|
||||||
|
egui::CollapsingHeader::new("Video FX")
|
||||||
|
.default_open(false)
|
||||||
|
.show(ui, |ui| {
|
||||||
|
let mut remove_idx = None;
|
||||||
|
for (i, fx) in self.profile.video_fx.iter().enumerate() {
|
||||||
|
let selected = self.selected_node == Some(SelectedNode::VideoFx(i));
|
||||||
|
if ui.selectable_label(selected, fx.name.to_string()).clicked() {
|
||||||
|
self.selected_node = Some(SelectedNode::VideoFx(i));
|
||||||
|
}
|
||||||
|
if ui.button("Remove").clicked() {
|
||||||
|
remove_idx = Some(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(idx) = remove_idx {
|
||||||
|
self.profile.video_fx.remove(idx);
|
||||||
|
match self.selected_node {
|
||||||
|
Some(SelectedNode::VideoFx(j)) if j == idx => self.selected_node = None,
|
||||||
|
Some(SelectedNode::VideoFx(j)) if j > idx => {
|
||||||
|
self.selected_node = Some(SelectedNode::VideoFx(j - 1));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ui.button("+ Add Video FX").clicked() {
|
||||||
|
let n = self.profile.video_fx.len();
|
||||||
|
self.profile.video_fx.push(crate::profile::VideoFx {
|
||||||
|
category: "Video FX".to_string(),
|
||||||
|
name: format!("new_fx_{}", n + 1),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
self.selected_node = Some(SelectedNode::VideoFx(n));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Item 2: detail editor (right pane). Renders whichever
|
||||||
|
/// item the user selected in the tree.
|
||||||
|
fn keybind_detail(&mut self, ui: &mut egui::Ui) {
|
||||||
|
match self.selected_node {
|
||||||
|
Some(SelectedNode::Map(i)) => {
|
||||||
|
if let Some(m) = self.profile.maps.get_mut(i) {
|
||||||
|
ui.heading(format!("Map #{}", i + 1));
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("category");
|
||||||
|
egui::ComboBox::from_label(format!("map_cat_{}", i))
|
||||||
|
.selected_text(m.category.clone())
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
for cat in ["Always On", "Combat", "Mapped Keys", "Character Sets", "Characters"] {
|
||||||
|
ui.selectable_value(&mut m.category, cat.to_string(), cat);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("name");
|
||||||
|
ui.text_edit_singleline(&mut m.name);
|
||||||
|
ui.label("hotkey");
|
||||||
|
ui.text_edit_singleline(&mut m.hotkey.0);
|
||||||
|
ui.checkbox(&mut m.hold, "hold");
|
||||||
|
});
|
||||||
|
ui.separator();
|
||||||
|
ui.label("Steps (executed in order on press)");
|
||||||
|
let mut step_remove = None;
|
||||||
|
for (si, s) in m.steps.iter_mut().enumerate() {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label(format!("{}", si + 1));
|
||||||
|
ui.label("bind");
|
||||||
|
let mut bind_str = s.bind.clone().unwrap_or_default();
|
||||||
|
if ui.text_edit_singleline(&mut bind_str).changed() {
|
||||||
|
s.bind = if bind_str.is_empty() { None } else { Some(bind_str) };
|
||||||
|
}
|
||||||
|
ui.label("key");
|
||||||
|
let mut key_str = s.key.clone().unwrap_or_default();
|
||||||
|
if ui.text_edit_singleline(&mut key_str).changed() {
|
||||||
|
s.key = if key_str.is_empty() { None } else { Some(key_str) };
|
||||||
|
}
|
||||||
|
ui.label("target");
|
||||||
|
ui.text_edit_singleline(&mut s.target);
|
||||||
|
if ui.button("x").clicked() {
|
||||||
|
step_remove = Some(si);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(si) = step_remove {
|
||||||
|
m.steps.remove(si);
|
||||||
|
}
|
||||||
|
if ui.button("+ Add step").clicked() {
|
||||||
|
m.steps.push(crate::profile::Step {
|
||||||
|
bind: None,
|
||||||
|
key: None,
|
||||||
|
delay_ms: None,
|
||||||
|
target: "all".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(SelectedNode::VideoFx(i)) => {
|
||||||
|
if let Some(fx) = self.profile.video_fx.get_mut(i) {
|
||||||
|
ui.heading(format!("Video FX #{}", i + 1));
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.checkbox(&mut fx.enabled, "on");
|
||||||
|
ui.label("name");
|
||||||
|
ui.text_edit_singleline(&mut fx.name);
|
||||||
|
ui.label("from slot");
|
||||||
|
ui.add(egui::DragValue::new(&mut fx.source_slot).range(1..=16));
|
||||||
|
ui.checkbox(&mut fx.pass_through, "clicks+keys through");
|
||||||
|
});
|
||||||
|
ui.label("source x y w h");
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add(egui::DragValue::new(&mut fx.source.x));
|
||||||
|
ui.add(egui::DragValue::new(&mut fx.source.y));
|
||||||
|
ui.add(egui::DragValue::new(&mut fx.source.w));
|
||||||
|
ui.add(egui::DragValue::new(&mut fx.source.h));
|
||||||
|
});
|
||||||
|
ui.label("viewer x y w h");
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add(egui::DragValue::new(&mut fx.viewer.x));
|
||||||
|
ui.add(egui::DragValue::new(&mut fx.viewer.y));
|
||||||
|
ui.add(egui::DragValue::new(&mut fx.viewer.w));
|
||||||
|
ui.add(egui::DragValue::new(&mut fx.viewer.h));
|
||||||
|
});
|
||||||
|
ui.label("fps");
|
||||||
|
ui.add(egui::DragValue::new(&mut fx.fps).range(1..=60));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
ui.label("Click an item on the left to edit it.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn page_teams(&mut self, ui: &mut egui::Ui) {
|
fn page_teams(&mut self, ui: &mut egui::Ui) {
|
||||||
|
|||||||
@ -179,6 +179,11 @@ pub struct Group {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||||
pub struct Map {
|
pub struct Map {
|
||||||
|
/// Item 2: tree category. One of: "Always On", "Combat",
|
||||||
|
/// "Mapped Keys", "Character Sets", "Characters". Defaults
|
||||||
|
/// to "Mapped Keys".
|
||||||
|
#[serde(default = "default_map_category")]
|
||||||
|
pub category: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub hotkey: Hotkey,
|
pub hotkey: Hotkey,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@ -207,6 +212,9 @@ fn default_target() -> String {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||||
pub struct VideoFx {
|
pub struct VideoFx {
|
||||||
|
/// Item 2: tree category. Always "Video FX" for now.
|
||||||
|
#[serde(default = "default_vfx_category")]
|
||||||
|
pub category: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
#[serde(default = "default_true")]
|
#[serde(default = "default_true")]
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
@ -324,6 +332,16 @@ fn default_one() -> f64 {
|
|||||||
1.0
|
1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Item 2: default category for new Map entries.
|
||||||
|
fn default_map_category() -> String {
|
||||||
|
"Mapped Keys".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Item 2: default category for new VideoFx entries.
|
||||||
|
fn default_vfx_category() -> String {
|
||||||
|
"Video FX".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
impl NormRect {
|
impl NormRect {
|
||||||
/// Values > 1 are pixels inside the window; otherwise fractions 0..=1 of the window.
|
/// Values > 1 are pixels inside the window; otherwise fractions 0..=1 of the window.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user