Vendor helgoboss/helgobox (ReaLearn) as basis for custom UI fork

Stripped upstream git history; starting point for replacing the native
SWELL/Win32 mapping UI with something more suited to bulk editing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Paul Lipscomb
2026-07-15 17:38:29 -04:00
parent 583ed77a67
commit e58f06d9fa
2232 changed files with 685575 additions and 1 deletions
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "helgobox-dialogs"
version = "0.1.0"
authors = ["Benjamin Klum <benjamin.klum@helgoboss.org>"]
edition = "2021"
publish = false
[dependencies]
derive_more.workspace = true
indexmap.workspace = true
[lints.clippy]
# Enum glob use is not really dangerous in this module. Non-critical.
enum_glob_use = "allow"
+759
View File
@@ -0,0 +1,759 @@
#![allow(non_camel_case_types, clippy::upper_case_acronyms)]
use indexmap::IndexMap;
use std::collections::HashSet;
use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
use std::ops::Add;
pub type Caption = &'static str;
pub struct ResourceInfo {
global_scope: Scope,
scopes: IndexMap<String, Scope>,
optional_dialog_ids: HashSet<Id>,
conditional_control_ids: HashSet<Id>,
named_ids: Vec<Id>,
}
/// Formats the info as C header file.
///
/// Useful if you want to preview the dialogs in Visual Studio.
pub struct ResourceInfoAsCHeaderCode<'a>(pub &'a ResourceInfo);
impl Display for ResourceInfoAsCHeaderCode<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
for id in &self.0.named_ids {
writeln!(f, "#define {} {}", id.name, id.value)?;
}
Ok(())
}
}
/// Formats the header as Rust code.
///
/// Uses a similar format like bindgen because previously, bindgen was used to translate
/// the C header file to Rust.
pub struct ResourceInfoAsRustCode<'a>(pub &'a ResourceInfo);
impl Display for ResourceInfoAsRustCode<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
// Write module opener
f.write_str("pub mod root {\n")?;
// Write scaling information
ScopeAsRustCode::new("GLOBAL", &self.0.global_scope).fmt(f)?;
for (key, scope) in self.0.scopes.iter() {
ScopeAsRustCode::new(key, scope).fmt(f)?;
}
// Write resource IDs
for id in &self.0.named_ids {
if self.0.optional_dialog_ids.contains(id)
|| self.0.conditional_control_ids.contains(id)
{
f.write_str(" #[allow(dead_code)]\n")?;
}
writeln!(f, " pub const {}: u32 = {};", id.name, id.value)?;
}
// Write module closer
f.write_str("}\n")?;
Ok(())
}
}
#[derive(Default)]
pub struct Resource {
pub dialogs: Vec<Dialog>,
}
impl Resource {
pub fn generate_info(&self, context: &Context) -> ResourceInfo {
ResourceInfo {
global_scope: context.global_scope,
scopes: context.scopes.clone(),
optional_dialog_ids: self.optional_dialog_ids().collect(),
conditional_control_ids: self.conditional_control_ids().collect(),
named_ids: self.named_ids().collect(),
}
}
fn named_ids(&self) -> impl Iterator<Item = Id> + '_ {
self.dialogs.iter().flat_map(|dialog| {
fn get_if_named(id: Id) -> Option<Id> {
if id.is_named() {
Some(id)
} else {
None
}
}
let named_dialog_id = get_if_named(dialog.id);
let named_control_ids = dialog
.controls
.iter()
.flat_map(|control| get_if_named(control.id));
named_dialog_id.into_iter().chain(named_control_ids)
})
}
fn optional_dialog_ids(&self) -> impl Iterator<Item = Id> + '_ {
self.dialogs.iter().filter(|d| d.optional).map(|d| d.id)
}
fn conditional_control_ids(&self) -> impl Iterator<Item = Id> + '_ {
self.dialogs.iter().flat_map(|dialog| {
dialog
.controls
.iter()
.filter(|control| !control.conditions.is_empty())
.map(|control| control.id)
})
}
}
impl Display for Resource {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
for (i, dialog) in self.dialogs.iter().enumerate() {
dialog.fmt(f)?;
if i < self.dialogs.len() - 1 {
f.write_str("\n\n")?;
}
}
Ok(())
}
}
#[derive(Clone, Default)]
pub struct Dialog {
pub id: Id,
pub optional: bool,
pub rect: Rect,
pub kind: DialogKind,
pub styles: Styles,
pub ex_styles: Styles,
pub caption: Caption,
pub font: Option<Font>,
pub controls: Vec<Control>,
}
#[derive(Clone, Default)]
pub struct Styles(pub Vec<Style>);
impl Display for Styles {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
for (i, style) in self.0.iter().enumerate() {
style.fmt(f)?;
if i < self.0.len() - 1 {
f.write_str(" | ")?;
}
}
Ok(())
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Default)]
pub struct Id {
value: u32,
name: &'static str,
}
impl Id {
fn is_named(&self) -> bool {
!self.name.is_empty()
}
}
#[derive(Copy, Clone)]
pub struct DialogScaling {
pub x_scale: f64,
pub y_scale: f64,
pub width_scale: f64,
pub height_scale: f64,
}
struct DialogScalingAsRustCode<'a> {
attr: &'a str,
scope: &'a str,
scaling: &'a DialogScaling,
}
impl<'a> DialogScalingAsRustCode<'a> {
pub fn new(attr: &'a str, scope: &'a str, scaling: &'a DialogScaling) -> Self {
Self {
attr,
scope,
scaling,
}
}
}
impl Display for DialogScalingAsRustCode<'_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
writeln!(
f,
" {}\n pub const {}_X_SCALE: f64 = {:.4};",
self.attr, self.scope, self.scaling.x_scale
)?;
writeln!(
f,
" {}\n pub const {}_Y_SCALE: f64 = {:.4};",
self.attr, self.scope, self.scaling.y_scale
)?;
writeln!(
f,
" {}\n pub const {}_WIDTH_SCALE: f64 = {:.4};",
self.attr, self.scope, self.scaling.width_scale
)?;
writeln!(
f,
" {}\n pub const {}_HEIGHT_SCALE: f64 = {:.4};",
self.attr, self.scope, self.scaling.height_scale
)?;
Ok(())
}
}
#[derive(Copy, Clone)]
pub struct ScopedContext<'a> {
pub(crate) context: &'a Context,
scope: Option<Scope>,
}
#[derive(Copy, Clone)]
pub struct Scope {
pub linux: OsSpecificSettings,
pub windows: OsSpecificSettings,
pub macos: OsSpecificSettings,
}
struct ScopeAsRustCode<'a> {
scope_name: &'a str,
scope: &'a Scope,
}
impl<'a> ScopeAsRustCode<'a> {
pub fn new(scope_name: &'a str, scope: &'a Scope) -> Self {
Self { scope_name, scope }
}
}
impl Display for ScopeAsRustCode<'_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let mut write_os = |os: &str, scaling: &DialogScaling| -> fmt::Result {
let attr = format!("#[cfg(target_os = {})]", Quoted(os));
DialogScalingAsRustCode::new(&attr, self.scope_name, scaling).fmt(f)?;
Ok(())
};
write_os("linux", &self.scope.linux.scaling)?;
write_os("windows", &self.scope.windows.scaling)?;
write_os("macos", &self.scope.macos.scaling)?;
Ok(())
}
}
impl Scope {
pub const fn settings_for_this_os(&self) -> &OsSpecificSettings {
#[cfg(target_os = "linux")]
{
&self.linux
}
#[cfg(target_os = "windows")]
{
&self.windows
}
#[cfg(target_os = "macos")]
{
&self.macos
}
}
}
#[derive(Copy, Clone)]
pub struct OsSpecificSettings {
pub scaling: DialogScaling,
}
pub fn rect(x: u32, y: u32, width: u32, height: u32) -> Rect {
Rect::new(x, y, width, height)
}
impl ScopedContext<'_> {
pub fn default_dialog(&self) -> Dialog {
self.context.default_dialog()
}
pub fn scale_width(&self, width: u32) -> u32 {
scale(self.scaling().width_scale, width)
}
pub fn scale_height(&self, height: u32) -> u32 {
scale(self.scaling().height_scale, height)
}
pub fn rect(&self, x: u32, y: u32, width: u32, height: u32) -> Rect {
self.rect_flexible(Rect::new(x, y, width, height))
}
pub fn rect_flexible(&self, rect: Rect) -> Rect {
let scaling = self.scaling();
Rect {
x: scale(scaling.x_scale, rect.x),
y: scale(scaling.y_scale, rect.y),
width: scale(scaling.width_scale, rect.width),
height: scale(scaling.height_scale, rect.height),
}
}
fn scaling(&self) -> DialogScaling {
self.scope
.as_ref()
.map(|s| s.settings_for_this_os().scaling)
.unwrap_or(self.context.global_scope.settings_for_this_os().scaling)
}
}
pub struct IdGenerator {
next_id_value: u32,
}
impl IdGenerator {
pub fn new(initial_id_value: u32) -> Self {
Self {
next_id_value: initial_id_value,
}
}
pub fn id(&mut self) -> Id {
Id {
value: self.next_id_value(),
name: "",
}
}
pub fn named_id(&mut self, name: &'static str) -> Id {
Id {
value: self.next_id_value(),
name,
}
}
fn next_id_value(&mut self) -> u32 {
let v = self.next_id_value;
self.next_id_value += 1;
v
}
}
pub struct Context {
pub default_dialog: Dialog,
pub global_scope: Scope,
// IndexMap instead of HashMap because we don't want the order to be always the same when
// writing the bindings file.
pub scopes: IndexMap<String, Scope>,
}
impl Context {
pub fn global(&self) -> ScopedContext {
ScopedContext {
context: self,
scope: None,
}
}
pub fn scoped<'a>(&'a self, scope: &'a str) -> ScopedContext<'a> {
let scope = *self.scopes.get(scope).expect("scope not found");
ScopedContext {
context: self,
scope: Some(scope),
}
}
pub fn default_dialog(&self) -> Dialog {
self.default_dialog.clone()
}
}
fn scale(scale: f64, value: u32) -> u32 {
(scale * value as f64).round() as _
}
impl Display for Id {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if self.name.is_empty() {
self.value.fmt(f)
} else {
self.name.fmt(f)
}
}
}
#[derive(Copy, Clone, derive_more::Display)]
pub enum DialogKind {
DIALOG,
DIALOGEX,
}
impl Default for DialogKind {
fn default() -> Self {
Self::DIALOG
}
}
#[derive(Clone, Default)]
pub struct Control {
pub id: Id,
/// Unlike in dialog, it's important to distinguish between Some and None because some
/// controls need an empty string.
pub caption: Option<Caption>,
pub kind: ControlKind,
pub sub_kind: Option<SubControlKind>,
pub rect: Rect,
pub styles: Styles,
pub conditions: HashSet<Condition>,
}
impl Add<Style> for Control {
type Output = Control;
fn add(mut self, rhs: Style) -> Self::Output {
self.styles.0.push(rhs);
self
}
}
impl Add<Condition> for Control {
type Output = Control;
fn add(mut self, rhs: Condition) -> Self::Output {
self.conditions.insert(rhs);
self
}
}
struct Quoted<D>(D);
impl<D: Display> Display for Quoted<D> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "\"{}\"", self.0)
}
}
struct LineBreaksEscaped<D>(D);
impl<D: Display> Display for LineBreaksEscaped<D> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
self.0
.to_string()
.replace("\r\n", "\\r\\n")
.replace('\n', "\\r\\n")
.fmt(f)
}
}
fn opt<T: Display>(v: &Option<T>) -> Option<String> {
let v = v.as_ref()?;
Some(v.to_string())
}
fn req<T: Display>(v: T) -> Option<String> {
Some(v.to_string())
}
impl Display for Dialog {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
writeln!(f, "{} {} {}", self.id, self.kind, self.rect)?;
if !self.styles.0.is_empty() {
writeln!(f, "STYLE {}", self.styles)?;
}
if !self.ex_styles.0.is_empty() {
writeln!(f, "EXSTYLE {}", self.ex_styles)?;
}
if !self.caption.is_empty() {
writeln!(f, "CAPTION {}", Quoted(self.caption))?;
}
if let Some(font) = self.font.as_ref() {
writeln!(f, "FONT {font}")?;
}
f.write_str("BEGIN\n")?;
if !self.controls.is_empty() {
for control in &self.controls {
#[cfg(target_os = "macos")]
if control.conditions.contains(&Condition::SkipOnMacOs) {
continue;
}
writeln!(f, " {control}")?;
}
}
f.write_str("END")?;
Ok(())
}
}
impl Display for Control {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let caption = opt(&self.caption.map(LineBreaksEscaped).map(Quoted));
let id = req(self.id);
let rect = req(self.rect);
let styles = if self.styles.0.is_empty() {
None
} else {
Some(self.styles.to_string())
};
let args = if self.kind == ControlKind::CONTROL {
vec![
caption,
id,
req(Quoted(self.sub_kind.unwrap())),
styles,
rect,
]
} else {
vec![caption, id, rect, styles]
};
let args: Vec<_> = args.into_iter().flatten().collect();
write!(f, "{} {}", self.kind, args.join(","))
}
}
#[derive(Copy, Clone, Eq, PartialEq, derive_more::Display)]
pub enum ControlKind {
LTEXT,
RTEXT,
COMBOBOX,
PUSHBUTTON,
CONTROL,
EDITTEXT,
GROUPBOX,
DEFPUSHBUTTON,
CTEXT,
}
impl Default for ControlKind {
fn default() -> Self {
Self::CTEXT
}
}
#[derive(Copy, Clone, derive_more::Display)]
pub enum SubControlKind {
Button,
Static,
msctls_trackbar32,
}
#[derive(Clone, Copy)]
pub struct Font {
pub name: &'static str,
pub size: u32,
}
impl Display for Font {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}, {}", self.size, Quoted(self.name))
}
}
#[derive(Copy, Clone, Default)]
pub struct Rect {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
}
impl Display for Rect {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}, {}, {}, {}", self.x, self.y, self.width, self.height)
}
}
impl Rect {
pub fn new(x: u32, y: u32, width: u32, height: u32) -> Self {
Self {
x,
y,
width,
height,
}
}
}
pub fn pushbutton(caption: Caption, id: Id, rect: Rect) -> Control {
Control {
id,
caption: Some(caption),
kind: ControlKind::PUSHBUTTON,
rect,
..Default::default()
}
}
pub fn groupbox(caption: Caption, id: Id, rect: Rect) -> Control {
Control {
id,
caption: Some(caption),
kind: ControlKind::GROUPBOX,
rect,
..Default::default()
}
}
pub fn defpushbutton(caption: Caption, id: Id, rect: Rect) -> Control {
Control {
id,
caption: Some(caption),
kind: ControlKind::DEFPUSHBUTTON,
rect,
..Default::default()
}
}
pub fn ltext(caption: Caption, id: Id, rect: Rect) -> Control {
Control {
id,
caption: Some(caption),
kind: ControlKind::LTEXT,
rect: fix_text_rect(rect),
..Default::default()
}
}
pub fn rtext(caption: Caption, id: Id, rect: Rect) -> Control {
Control {
id,
caption: Some(caption),
kind: ControlKind::RTEXT,
rect: fix_text_rect(rect),
..Default::default()
}
}
pub fn ctext(caption: Caption, id: Id, rect: Rect) -> Control {
Control {
id,
caption: Some(caption),
kind: ControlKind::CTEXT,
rect: fix_text_rect(rect),
..Default::default()
}
}
pub fn combobox(id: Id, rect: Rect) -> Control {
Control {
id,
kind: ControlKind::COMBOBOX,
rect,
..Default::default()
}
}
pub fn edittext(id: Id, rect: Rect) -> Control {
Control {
id,
kind: ControlKind::EDITTEXT,
rect,
..Default::default()
}
}
pub fn control(caption: Caption, id: Id, sub_kind: SubControlKind, rect: Rect) -> Control {
Control {
id,
caption: Some(caption),
kind: ControlKind::CONTROL,
sub_kind: Some(sub_kind),
rect,
..Default::default()
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub enum Condition {
/// Doesn't output the control in the RC file generated on macOS.
///
/// Still assigns an ID because it's better to keep the bindings file the same on every
/// platform.
SkipOnMacOs,
}
#[derive(Copy, Clone, derive_more::Display)]
pub enum Style {
DS_SETFONT,
DS_MODALFRAME,
DS_3DLOOK,
DS_FIXEDSYS,
DS_CENTER,
WS_POPUP,
WS_VISIBLE,
WS_CAPTION,
WS_SYSMENU,
WS_THICKFRAME,
WS_MAXIMIZEBOX,
DS_CONTROL,
WS_CHILD,
CBS_DROPDOWNLIST,
CBS_HASSTRINGS,
ES_MULTILINE,
ES_READONLY,
ES_WANTRETURN,
WS_VSCROLL,
WS_TABSTOP,
WS_GROUP,
WS_DISABLED,
BS_AUTOCHECKBOX,
BS_AUTORADIOBUTTON,
TBS_BOTH,
TBS_NOTICKS,
SS_ETCHEDHORZ,
SS_LEFTNOWORDWRAP,
ES_AUTOHSCROLL,
SS_CENTERIMAGE,
SS_WORDELLIPSIS,
// With negation
#[display(fmt = "NOT WS_TABSTOP")]
NOT_WS_TABSTOP,
#[display(fmt = "NOT WS_GROUP")]
NOT_WS_GROUP,
// Ex styles
WS_EX_TOPMOST,
WS_EX_WINDOWEDGE,
}
/// Makes sure the effective (already scaled) height of a text is not too low.
pub fn fix_text_rect(rect: Rect) -> Rect {
Rect {
height: rect.height.max(MIN_EFFECTIVE_TEXT_HEIGHT),
..rect
}
}
pub struct Adder(pub u32);
impl Adder {
pub fn space(&mut self, units: u32) -> u32 {
self.0 += units;
self.0
}
pub fn span(&mut self, units: u32) -> u32 {
self.0 += units;
units
}
pub fn get(&self) -> u32 {
self.0
}
}
impl From<Adder> for u32 {
fn from(v: Adder) -> Self {
v.0
}
}
// If lower than this, text will be cut off, especially the part below the baseline.
#[cfg(target_os = "windows")]
const MIN_EFFECTIVE_TEXT_HEIGHT: u32 = 8;
// If lower than this, radio buttons will be cut off.
#[cfg(target_os = "macos")]
const MIN_EFFECTIVE_TEXT_HEIGHT: u32 = 10;
#[cfg(target_os = "linux")]
const MIN_EFFECTIVE_TEXT_HEIGHT: u32 = 13;
@@ -0,0 +1,11 @@
use crate::base::*;
pub fn create(context: ScopedContext, ids: &mut IdGenerator) -> Dialog {
use Style::*;
Dialog {
id: ids.named_id("ID_COLOR_PANEL"),
rect: context.rect(0, 0, 250, 250),
styles: Styles(vec![DS_SETFONT, DS_CONTROL, WS_CHILD, WS_VISIBLE]),
..context.default_dialog()
}
}
@@ -0,0 +1,12 @@
// Attention: We can't calculate a constant main panel height at this point because different
// scaling factors will be applied to the header panel, depending on the operating system.
pub const MAIN_PANEL_WIDTH: u32 = 470;
pub const HEADER_PANEL_HEIGHT: u32 = 124;
pub const HEADER_PANEL_WIDTH: u32 = MAIN_PANEL_WIDTH;
// Need to leave some space for the scrollbar.
pub const MAPPING_ROW_PANEL_WIDTH: u32 = MAIN_PANEL_WIDTH - 10;
pub const MAPPING_ROW_PANEL_HEIGHT: u32 = 48;
pub const FOOTER_PANEL_HEIGHT: u32 = 43;
pub const MAPPING_ROW_COUNT: u32 = 5;
pub const MAPPING_ROWS_PANEL_WIDTH: u32 = MAIN_PANEL_WIDTH;
pub const MAPPING_ROWS_PANEL_HEIGHT: u32 = MAPPING_ROW_PANEL_HEIGHT * MAPPING_ROW_COUNT;
@@ -0,0 +1,22 @@
use crate::base::*;
pub fn create(context: ScopedContext, ids: &mut IdGenerator) -> Dialog {
use Style::*;
Dialog {
id: ids.named_id("ID_EMPTY_PANEL"),
optional: true,
caption: "Editor",
rect: context.rect(0, 0, 600, 250),
styles: Styles(vec![
// Places the window into the center by default
DS_CENTER,
// Displays a close button
WS_SYSMENU,
// Displays a maximize button
WS_MAXIMIZEBOX,
// Allows user to change size of window
WS_THICKFRAME,
]),
..context.default_dialog()
}
}
+43
View File
@@ -0,0 +1,43 @@
use crate::base::*;
impl ScopedContext<'_> {
pub fn checkbox(&self, caption: Caption, id: Id, rect: Rect) -> Control {
use Style::*;
// We want to completely ignore the given checkbox height, but we want it to scale.
let fixed_rect = self.rect_flexible(Rect { height: 8, ..rect });
control(
caption,
id,
SubControlKind::Button,
fix_text_rect(fixed_rect),
) + BS_AUTOCHECKBOX
}
}
pub fn ok_button(id: Id, rect: Rect) -> Control {
defpushbutton("OK", id, rect)
}
pub fn dropdown(id: Id, rect: Rect) -> Control {
use Style::*;
combobox(id, rect) + CBS_DROPDOWNLIST + CBS_HASSTRINGS
}
pub fn slider(id: Id, rect: Rect) -> Control {
use Style::*;
control("", id, SubControlKind::msctls_trackbar32, rect) + TBS_BOTH + TBS_NOTICKS
}
pub fn radio_button(caption: Caption, id: Id, rect: Rect) -> Control {
use Style::*;
control(caption, id, SubControlKind::Button, fix_text_rect(rect)) + BS_AUTORADIOBUTTON
}
pub fn divider(id: Id, rect: Rect) -> Control {
use Style::*;
control("", id, SubControlKind::Static, rect) + SS_ETCHEDHORZ
}
pub fn static_text(caption: Caption, id: Id, rect: Rect) -> Control {
control(caption, id, SubControlKind::Static, fix_text_rect(rect))
}
@@ -0,0 +1,27 @@
use crate::base::*;
use crate::ext::*;
pub fn create(context: ScopedContext, ids: &mut IdGenerator) -> Dialog {
use Style::*;
Dialog {
id: ids.named_id("ID_GROUP_PANEL"),
caption: "Edit group",
rect: context.rect(0, 0, 444, 74),
styles: Styles(vec![
DS_SETFONT,
DS_MODALFRAME,
DS_3DLOOK,
DS_FIXEDSYS,
DS_CENTER,
WS_POPUP,
WS_VISIBLE,
WS_CAPTION,
WS_SYSMENU,
]),
controls: vec![ok_button(
ids.named_id("ID_GROUP_PANEL_OK"),
context.rect(197, 53, 50, 14),
)],
..context.default_dialog()
}
}
@@ -0,0 +1,238 @@
use crate::base::*;
use crate::constants::{HEADER_PANEL_HEIGHT, HEADER_PANEL_WIDTH};
use crate::ext::*;
pub fn create(context: ScopedContext, ids: &mut IdGenerator) -> Dialog {
use Style::*;
let text_height = 9;
let left_label_x = 7;
let io_label_width = 24;
let space = 3;
let (io_combo_box_width, io_combo_box_height) = (190, 14);
let mut row1 = Adder(left_label_x);
let mut row2 = Adder(left_label_x);
let upper_part_controls = [
// Input/output
ltext(
"Input",
ids.id(),
context.rect(row1.get(), 5, row1.span(io_label_width), text_height),
),
pushbutton(
"MIDI: <FX input>",
ids.named_id("ID_CONTROL_INPUT_BUTTON"),
context.rect(
row1.space(space),
3,
row1.span(io_combo_box_width),
io_combo_box_height,
),
) + NOT_WS_TABSTOP,
ltext(
"Output",
ids.id(),
context.rect(row2.get(), 25, row2.span(io_label_width), text_height),
),
pushbutton(
"<None>",
ids.named_id("ID_FEEDBACK_OUTPUT_BUTTON"),
context.rect(
row2.space(space),
23,
row2.span(io_combo_box_width),
io_combo_box_height,
),
) + NOT_WS_TABSTOP,
// Quick actions
pushbutton(
"Menu",
ids.named_id("ID_MENU_BUTTON"),
context.rect(row1.space(space), 3, row1.span(27), 14),
) + WS_GROUP,
pushbutton(
"Import from clipboard",
ids.named_id("ID_IMPORT_BUTTON"),
context.rect(row1.space(space), 3, row1.span(73), 14),
) + WS_GROUP,
pushbutton(
"Export to clipboard",
ids.named_id("ID_EXPORT_BUTTON"),
context.rect(row1.space(space), 3, row1.span(67), 14),
) + NOT_WS_TABSTOP,
pushbutton(
"Projection",
ids.named_id("ID_PROJECTION_BUTTON"),
context.rect(row1.space(space), 3, row1.span(42), 14),
) + NOT_WS_TABSTOP,
pushbutton(
"?",
ids.named_id("ID_MAIN_HELP_BUTTON"),
context.rect(row1.space(space), 3, row1.span(14), 14),
) + NOT_WS_TABSTOP,
// Event filter
ltext(
"Let MIDI through:",
ids.named_id("ID_LET_THROUGH_LABEL_TEXT"),
context.rect(257, 25, 55, 9),
),
context.checkbox(
"Matched events",
ids.named_id("ID_LET_MATCHED_EVENTS_THROUGH_CHECK_BOX"),
rect(319, 25, 67, 8),
) + WS_TABSTOP,
context.checkbox(
"Unmatched events",
ids.named_id("ID_LET_UNMATCHED_EVENTS_THROUGH_CHECK_BOX"),
rect(392, 25, 76, 8),
) + WS_TABSTOP,
];
let show_controls = [
ltext(
"Show",
ids.named_id("ID_HEADER_PANEL_SHOW_LABEL_TEXT"),
context.rect(7, 47, 24, 9),
),
radio_button(
"Controller compartment (for describing your device, optional)",
ids.named_id("ID_CONTROLLER_COMPARTMENT_RADIO_BUTTON"),
context.rect(50, 47, 200, 8),
) + WS_TABSTOP,
radio_button(
"Main compartment (for defining what it should do)",
ids.named_id("ID_MAIN_COMPARTMENT_RADIO_BUTTON"),
context.rect(269, 47, 200, 8),
) + WS_TABSTOP,
];
let lower_part_controls = [
// Preset
ltext(
"Controller preset",
ids.named_id("ID_PRESET_LABEL_TEXT"),
context.rect(7, 69, 57, 9),
),
pushbutton(
"<None>",
ids.named_id("ID_PRESET_BROWSE_BUTTON"),
context.rect(68, 66, 135, 14),
) + WS_GROUP,
// Preset actions
pushbutton(
"Save",
ids.named_id("ID_PRESET_SAVE_BUTTON"),
context.rect(207, 66, 26, 14),
) + NOT_WS_TABSTOP,
pushbutton(
"Save as...",
ids.named_id("ID_PRESET_SAVE_AS_BUTTON"),
context.rect(235, 66, 42, 14),
) + WS_GROUP,
pushbutton(
"Delete",
ids.named_id("ID_PRESET_DELETE_BUTTON"),
context.rect(279, 66, 28, 14),
) + NOT_WS_TABSTOP,
// Auto-load
ltext(
"Auto-load",
ids.named_id("ID_AUTO_LOAD_LABEL_TEXT"),
context.rect(319, 69, 33, 9),
) + NOT_WS_GROUP,
dropdown(
ids.named_id("ID_AUTO_LOAD_COMBO_BOX"),
context.rect(356, 67, 107, 16),
) + WS_VSCROLL
+ WS_GROUP
+ WS_TABSTOP,
// Mapping group
ltext("Mapping group", ids.id(), context.rect(7, 89, 55, 9)) + NOT_WS_GROUP,
dropdown(
ids.named_id("ID_GROUP_COMBO_BOX"),
context.rect(68, 87, 135, 16),
) + WS_VSCROLL
+ WS_TABSTOP,
// Mapping group actions
pushbutton(
"Add",
ids.named_id("ID_GROUP_ADD_BUTTON"),
context.rect(207, 86, 26, 14),
) + WS_GROUP,
pushbutton(
"Remove",
ids.named_id("ID_GROUP_DELETE_BUTTON"),
context.rect(235, 86, 42, 14),
) + NOT_WS_TABSTOP,
pushbutton(
"Edit",
ids.named_id("ID_GROUP_EDIT_BUTTON"),
context.rect(279, 86, 28, 14),
) + NOT_WS_TABSTOP,
pushbutton(
"Notes",
ids.named_id("ID_NOTES_BUTTON"),
context.rect(426, 86, 35, 14),
) + NOT_WS_TABSTOP,
// Mapping list actions
ltext("Mappings", ids.id(), context.rect(7, 109, 33, 9)) + NOT_WS_GROUP,
pushbutton(
"Add one",
ids.named_id("ID_ADD_MAPPING_BUTTON"),
context.rect(42, 106, 41, 14),
) + WS_GROUP,
pushbutton(
"Learn many",
ids.named_id("ID_LEARN_MANY_MAPPINGS_BUTTON"),
context.rect(86, 106, 47, 14),
) + NOT_WS_TABSTOP,
// Search
ltext("Search", ids.id(), context.rect(139, 109, 25, 9)) + NOT_WS_GROUP,
edittext(
ids.named_id("ID_HEADER_SEARCH_EDIT_CONTROL"),
context.rect(165, 106, 157, 14),
) + ES_AUTOHSCROLL,
pushbutton(
"X",
ids.named_id("ID_CLEAR_SEARCH_BUTTON"),
context.rect(323, 106, 11, 14),
) + NOT_WS_TABSTOP,
// Source filter
pushbutton(
"Filter source",
ids.named_id("ID_FILTER_BY_SOURCE_BUTTON"),
context.rect(340, 106, 48, 14),
) + WS_GROUP,
pushbutton(
"X",
ids.named_id("ID_CLEAR_SOURCE_FILTER_BUTTON"),
context.rect(389, 106, 11, 14),
) + NOT_WS_TABSTOP,
// Target filter
pushbutton(
"Filter target",
ids.named_id("ID_FILTER_BY_TARGET_BUTTON"),
context.rect(404, 106, 45, 14),
) + WS_GROUP,
pushbutton(
"X",
ids.named_id("ID_CLEAR_TARGET_FILTER_BUTTON"),
context.rect(450, 106, 11, 14),
) + NOT_WS_TABSTOP,
];
let divider_controls = [
divider(ids.id(), context.rect(0, 41, HEADER_PANEL_WIDTH, 1)),
divider(ids.id(), context.rect(0, 62, HEADER_PANEL_WIDTH, 1)),
// divider(ids.id(), context.rect(0, 123, HEADER_PANEL_WIDTH, 1)),
];
Dialog {
id: ids.named_id("ID_HEADER_PANEL"),
kind: DialogKind::DIALOGEX,
rect: context.rect(0, 0, HEADER_PANEL_WIDTH, HEADER_PANEL_HEIGHT),
styles: Styles(vec![DS_SETFONT, DS_CONTROL, WS_CHILD, WS_VISIBLE]),
controls: upper_part_controls
.into_iter()
.chain(show_controls)
.chain(lower_part_controls)
.chain(divider_controls)
.collect(),
..context.default_dialog()
}
}
@@ -0,0 +1,9 @@
use crate::base::*;
pub fn create(context: ScopedContext, ids: &mut IdGenerator) -> Dialog {
Dialog {
id: ids.named_id("ID_HIDDEN_PANEL"),
caption: "Hidden panel",
..context.default_dialog()
}
}
@@ -0,0 +1,27 @@
use crate::base::*;
use crate::constants::{FOOTER_PANEL_HEIGHT, MAIN_PANEL_WIDTH};
pub fn create(
context: ScopedContext,
ids: &mut IdGenerator,
effective_header_panel_height: u32,
effective_rows_panel_height: u32,
) -> Dialog {
use Style::*;
let controls = vec![];
Dialog {
id: ids.named_id("ID_INSTANCE_PANEL"),
kind: DialogKind::DIALOGEX,
rect: Rect::new(
0,
0,
context.scale_width(MAIN_PANEL_WIDTH),
effective_header_panel_height
+ effective_rows_panel_height
+ context.scale_height(FOOTER_PANEL_HEIGHT),
),
styles: Styles(vec![DS_SETFONT, DS_CONTROL, WS_CHILD, WS_VISIBLE]),
controls,
..context.default_dialog()
}
}
+198
View File
@@ -0,0 +1,198 @@
use crate::base::{
Context, Dialog, DialogScaling, Font, IdGenerator, OsSpecificSettings, Resource,
ResourceInfoAsCHeaderCode, ResourceInfoAsRustCode, Scope,
};
use std::io::Write;
use std::path::Path;
mod base;
mod color_panel;
pub mod constants;
mod empty_panel;
mod ext;
mod group_panel;
mod header_panel;
mod hidden_panel;
mod instance_panel;
mod mapping_panel;
mod mapping_row_panel;
mod mapping_rows_panel;
mod message_panel;
mod shared_group_mapping_panel;
mod simple_editor_panel;
mod unit_panel;
mod welcome_panel;
pub fn generate_dialog_files(rc_dir: impl AsRef<Path>, bindings_file: impl AsRef<Path>) {
let default_font = Font {
name: "Ms Shell Dlg",
size: 8,
};
let default_dialog = Dialog {
font: Some(default_font),
..Default::default()
};
let default_scaling = {
let horizontal_scale = 1.0;
let vertical_scale = 1.0;
DialogScaling {
x_scale: horizontal_scale,
y_scale: vertical_scale,
width_scale: horizontal_scale,
height_scale: vertical_scale,
}
};
let global_scope = {
Scope {
linux: {
let horizontal_scale = 1.0;
let vertical_scale = 1.0;
OsSpecificSettings {
scaling: DialogScaling {
x_scale: horizontal_scale,
y_scale: vertical_scale,
width_scale: horizontal_scale,
height_scale: vertical_scale,
},
}
},
windows: OsSpecificSettings {
scaling: default_scaling,
},
macos: {
let horizontal_scale = 1.0;
let vertical_scale = 1.0;
OsSpecificSettings {
scaling: DialogScaling {
x_scale: horizontal_scale,
y_scale: vertical_scale,
width_scale: horizontal_scale,
height_scale: vertical_scale,
},
}
},
}
};
let header_panel_scope = {
let horizontal_scale = 1.0;
let vertical_scale = 0.8;
Scope {
windows: OsSpecificSettings {
scaling: DialogScaling {
x_scale: horizontal_scale,
y_scale: vertical_scale,
width_scale: horizontal_scale,
height_scale: vertical_scale,
},
},
..global_scope
}
};
let mapping_panel_scope = {
Scope {
windows: {
let horizontal_scale = 1.0;
let vertical_scale = 0.8;
OsSpecificSettings {
scaling: DialogScaling {
x_scale: horizontal_scale,
y_scale: vertical_scale,
width_scale: horizontal_scale,
height_scale: vertical_scale,
},
}
},
macos: {
let horizontal_scale = 1.0;
let vertical_scale = 0.92;
OsSpecificSettings {
scaling: DialogScaling {
x_scale: horizontal_scale,
y_scale: vertical_scale,
width_scale: horizontal_scale,
height_scale: vertical_scale,
},
}
},
..global_scope
}
};
let mut ids = IdGenerator::new(30_000);
let context = Context {
default_dialog,
scopes: [
("MAPPING_PANEL", mapping_panel_scope),
("HEADER_PANEL", header_panel_scope),
]
.into_iter()
.map(|(key, value)| (key.to_string(), value))
.collect(),
global_scope,
};
let group_panel_dialog = group_panel::create(context.scoped("MAPPING_PANEL"), &mut ids);
let header_panel_dialog = header_panel::create(context.scoped("HEADER_PANEL"), &mut ids);
let mapping_panel_dialog = mapping_panel::create(context.scoped("MAPPING_PANEL"), &mut ids);
let mapping_row_panel_dialog = mapping_row_panel::create(context.global(), &mut ids);
let mapping_rows_panel_dialog = mapping_rows_panel::create(context.global(), &mut ids);
let message_panel_dialog = message_panel::create(context.global(), &mut ids);
let shared_group_mapping_panel_dialog =
shared_group_mapping_panel::create(context.scoped("MAPPING_PANEL"), &mut ids);
let unit_panel_dialog = {
instance_panel::create(
context.global(),
&mut ids,
header_panel_dialog.rect.height,
mapping_rows_panel_dialog.rect.height,
)
};
let main_panel_dialog = {
unit_panel::create(
context.global(),
&mut ids,
header_panel_dialog.rect.height,
mapping_rows_panel_dialog.rect.height,
)
};
let simple_editor_panel_dialog = simple_editor_panel::create(context.global(), &mut ids);
let empty_panel_dialog = empty_panel::create(context.global(), &mut ids);
let setup_panel_dialog = welcome_panel::create(context.global(), &mut ids);
let color_panel_dialog = color_panel::create(context.global(), &mut ids);
let resource = Resource {
dialogs: vec![
group_panel_dialog,
header_panel_dialog,
mapping_panel_dialog,
mapping_row_panel_dialog,
mapping_rows_panel_dialog,
message_panel_dialog,
shared_group_mapping_panel_dialog,
unit_panel_dialog,
main_panel_dialog,
simple_editor_panel_dialog,
empty_panel_dialog,
setup_panel_dialog,
color_panel_dialog,
hidden_panel::create(context.global(), &mut ids),
],
};
let header_info = resource.generate_info(&context);
// Write C header file (in case we want to use a resource editor to preview the dialogs)
let c_header_code = ResourceInfoAsCHeaderCode(&header_info).to_string();
std::fs::write(rc_dir.as_ref().join("resource.h"), c_header_code)
.expect("couldn't write C header file");
// Write Rust file (so we don't have to do it via bindgen, which is slow)
let rust_code = ResourceInfoAsRustCode(&header_info).to_string();
std::fs::write(bindings_file, rust_code).expect("couldn't write Rust bindings file");
// Write rc file
let rc_file_header = include_str!("rc_file_header.txt");
let rc_file_footer = include_str!("rc_file_footer.txt");
let rc_file_content = format!("{rc_file_header}\n\n{resource}\n\n{rc_file_footer}");
let mut output = Vec::new();
// Write UTF_16LE BOM
output.write_all(&[0xFF, 0xFE]).unwrap();
// Write UTF_16LE contents
for utf16 in rc_file_content.encode_utf16() {
output.write_all(&utf16.to_le_bytes()).unwrap();
}
std::fs::write(rc_dir.as_ref().join("msvc.rc"), output).expect("couldn't write rc file");
}
@@ -0,0 +1,772 @@
use crate::base::*;
use crate::ext::*;
pub fn create(context: ScopedContext, ids: &mut IdGenerator) -> Dialog {
use Condition::*;
use Style::*;
let mapping_controls = [
// groupbox(
// "Mapping",
// ids.named_id("ID_MAPPING_PANEL_LABEL"),
// context.rect(7, 1, 435, 67),
// ),
ltext(
"Mapping",
ids.named_id("ID_MAPPING_PANEL_MAPPING_LABEL"),
context.rect(7, 1, 435, 9),
),
ltext(
"Feedback",
ids.named_id("ID_MAPPING_PANEL_FEEDBACK_LABEL"),
context.rect(11, 53, 34, 9),
) + NOT_WS_GROUP,
combobox(
ids.named_id("ID_MAPPING_FEEDBACK_SEND_BEHAVIOR_COMBO_BOX"),
context.rect(48, 51, 120, 15),
) + CBS_DROPDOWNLIST
+ CBS_HASSTRINGS
+ WS_TABSTOP,
context.checkbox(
"Show in projection",
ids.named_id("ID_MAPPING_SHOW_IN_PROJECTION_CHECK_BOX"),
rect(180, 53, 74, 8),
) + WS_GROUP
+ WS_TABSTOP,
pushbutton(
"Advanced settings",
ids.named_id("ID_MAPPING_ADVANCED_BUTTON"),
context.rect(259, 50, 87, 14),
) + NOT_WS_TABSTOP,
pushbutton(
"Find in mapping list",
ids.named_id("ID_MAPPING_FIND_IN_LIST_BUTTON"),
context.rect(352, 50, 87, 14),
) + NOT_WS_TABSTOP,
];
let source_controls = [
// groupbox(
// "Source",
// ids.named_id("ID_SOURCE_PANEL_LABEL"),
// context.rect(7, 67, 165, 165),
// ) + WS_GROUP,
ltext(
"Source",
ids.named_id("ID_MAPPING_PANEL_SOURCE_LABEL"),
context.rect(7, 67, 165, 9),
) + WS_GROUP,
pushbutton(
"Learn",
ids.named_id("ID_SOURCE_LEARN_BUTTON"),
context.rect(11, 77, 157, 14),
),
ltext(
"Category",
ids.named_id("ID_MAPPING_PANEL_SOURCE_CATEGORY_LABEL"),
context.rect(11, 98, 31, 9),
) + NOT_WS_GROUP,
dropdown(
ids.named_id("ID_SOURCE_CATEGORY_COMBO_BOX"),
context.rect(48, 96, 120, 15),
) + WS_TABSTOP,
ltext(
"Type",
ids.named_id("ID_SOURCE_TYPE_LABEL_TEXT"),
context.rect(11, 118, 32, 9),
) + NOT_WS_GROUP,
dropdown(
ids.named_id("ID_SOURCE_TYPE_COMBO_BOX"),
context.rect(48, 116, 120, 15),
) + WS_VSCROLL
+ WS_TABSTOP,
ltext(
"Message",
ids.named_id("ID_SOURCE_MIDI_MESSAGE_TYPE_LABEL_TEXT"),
context.rect(11, 138, 30, 9),
) + NOT_WS_GROUP,
ltext(
"Channel",
ids.named_id("ID_SOURCE_CHANNEL_LABEL"),
context.rect(11, 138, 32, 9),
) + NOT_WS_GROUP,
dropdown(
ids.named_id("ID_SOURCE_CHANNEL_COMBO_BOX"),
context.rect(48, 136, 120, 15),
) + WS_VSCROLL
+ WS_TABSTOP,
edittext(
ids.named_id("ID_SOURCE_LINE_3_EDIT_CONTROL"),
context.rect(48, 135, 120, 14),
) + ES_AUTOHSCROLL,
ltext(
"Note/CC number",
ids.named_id("ID_SOURCE_NOTE_OR_CC_NUMBER_LABEL_TEXT"),
context.rect(11, 158, 34, 9),
) + NOT_WS_GROUP,
context.checkbox(
"RPN",
ids.named_id("ID_SOURCE_RPN_CHECK_BOX"),
rect(48, 158, 30, 8),
) + WS_TABSTOP,
dropdown(
ids.named_id("ID_SOURCE_LINE_4_COMBO_BOX_1"),
context.rect(47, 156, 26, 15),
) + WS_TABSTOP,
edittext(
ids.named_id("ID_SOURCE_NUMBER_EDIT_CONTROL"),
context.rect(87, 155, 80, 14),
) + ES_AUTOHSCROLL,
dropdown(
ids.named_id("ID_SOURCE_NUMBER_COMBO_BOX"),
context.rect(84, 156, 84, 15),
) + WS_VSCROLL
+ WS_TABSTOP,
pushbutton(
"Pick",
ids.named_id("ID_SOURCE_LINE_4_BUTTON"),
context.rect(47, 155, 26, 14),
),
ltext(
"Character",
ids.named_id("ID_SOURCE_CHARACTER_LABEL_TEXT"),
context.rect(11, 178, 32, 9),
) + NOT_WS_GROUP,
dropdown(
ids.named_id("ID_SOURCE_CHARACTER_COMBO_BOX"),
context.rect(48, 176, 120, 15),
) + WS_TABSTOP,
pushbutton(
"Pick",
ids.named_id("ID_SOURCE_LINE_5_BUTTON"),
context.rect(47, 176, 26, 14),
),
edittext(
ids.named_id("ID_SOURCE_LINE_5_EDIT_CONTROL"),
context.rect(87, 176, 80, 14),
) + ES_AUTOHSCROLL,
context.checkbox(
"14-bit values",
ids.named_id("ID_SOURCE_14_BIT_CHECK_BOX"),
rect(47, 192, 56, 8),
) + WS_TABSTOP,
ltext(
"Address",
ids.named_id("ID_SOURCE_OSC_ADDRESS_LABEL_TEXT"),
context.rect(11, 202, 139, 9),
) + NOT_WS_GROUP,
edittext(
ids.named_id("ID_SOURCE_OSC_ADDRESS_PATTERN_EDIT_CONTROL"),
context.rect(11, 213, 140, 14),
) + ES_AUTOHSCROLL,
pushbutton(
"...",
ids.named_id("ID_SOURCE_SCRIPT_DETAIL_BUTTON"),
context.rect(155, 213, 13, 14),
),
];
let target_controls = [
// groupbox(
// "Target",
// ids.named_id("ID_TARGET_PANEL_LABEL"),
// context.rect(177, 67, 265, 165),
// ),
ltext(
"Target",
ids.named_id("ID_MAPPING_PANEL_TARGET_LABEL"),
context.rect(177, 67, 265, 9),
),
pushbutton(
"Learn",
ids.named_id("ID_TARGET_LEARN_BUTTON"),
context.rect(181, 77, 50, 14),
) + WS_GROUP,
pushbutton(
"Menu",
ids.named_id("ID_TARGET_MENU_BUTTON"),
context.rect(236, 77, 42, 14),
) + NOT_WS_TABSTOP,
ltext(
"Hint",
ids.named_id("ID_TARGET_HINT"),
context.rect(285, 80, 155, 9),
) + WS_TABSTOP,
ltext(
"Type",
ids.named_id("ID_MAPPING_PANEL_TARGET_TYPE_LABEL"),
context.rect(181, 98, 35, 9),
) + NOT_WS_GROUP,
dropdown(
ids.named_id("ID_TARGET_CATEGORY_COMBO_BOX"),
context.rect(220, 96, 58, 15),
) + WS_TABSTOP,
pushbutton(
"Target type",
ids.named_id("ID_TARGET_TYPE_BUTTON"),
context.rect(283, 96, 155, 15),
),
ltext(
"Action name",
ids.named_id("ID_TARGET_LINE_2_LABEL_2"),
context.rect(220, 118, 189, 9),
) + NOT_WS_GROUP,
ltext(
"Hint",
ids.named_id("ID_TARGET_LINE_2_LABEL_3"),
context.rect(412, 118, 26, 9),
) + NOT_WS_GROUP,
ltext(
"Line 2",
ids.named_id("ID_TARGET_LINE_2_LABEL_1"),
context.rect(181, 118, 35, 9),
) + NOT_WS_GROUP,
dropdown(
ids.named_id("ID_TARGET_LINE_2_COMBO_BOX_1"),
context.rect(220, 116, 58, 30),
) + WS_VSCROLL
+ WS_TABSTOP,
edittext(
ids.named_id("ID_TARGET_LINE_2_EDIT_CONTROL"),
context.rect(282, 115, 127, 14),
) + ES_AUTOHSCROLL,
dropdown(
ids.named_id("ID_TARGET_LINE_2_COMBO_BOX_2"),
context.rect(283, 116, 127, 30),
) + WS_VSCROLL
+ WS_TABSTOP,
pushbutton(
"Pick",
ids.named_id("ID_TARGET_LINE_2_BUTTON"),
context.rect(412, 114, 26, 14),
),
ltext(
"Line 3",
ids.named_id("ID_TARGET_LINE_3_LABEL_1"),
context.rect(181, 138, 35, 9),
) + NOT_WS_GROUP,
dropdown(
ids.named_id("ID_TARGET_LINE_3_COMBO_BOX_1"),
context.rect(220, 136, 58, 30),
) + WS_VSCROLL
+ WS_TABSTOP,
edittext(
ids.named_id("ID_TARGET_LINE_3_EDIT_CONTROL"),
context.rect(282, 135, 127, 14),
) + ES_AUTOHSCROLL,
dropdown(
ids.named_id("ID_TARGET_LINE_3_COMBO_BOX_2"),
context.rect(283, 136, 155, 30),
) + WS_VSCROLL
+ WS_TABSTOP,
ltext(
"Parameter",
ids.named_id("ID_TARGET_LINE_3_LABEL_2"),
context.rect(220, 138, 189, 9),
) + NOT_WS_GROUP,
ltext(
"Hint",
ids.named_id("ID_TARGET_LINE_3_LABEL_3"),
context.rect(412, 138, 26, 9),
) + NOT_WS_GROUP,
pushbutton(
"Pick",
ids.named_id("ID_TARGET_LINE_3_BUTTON"),
context.rect(412, 134, 26, 14),
),
ltext(
"Line 4",
ids.named_id("ID_TARGET_LINE_4_LABEL_1"),
context.rect(181, 158, 35, 9),
) + NOT_WS_GROUP,
dropdown(
ids.named_id("ID_TARGET_LINE_4_COMBO_BOX_1"),
context.rect(220, 156, 58, 30),
) + WS_VSCROLL
+ WS_TABSTOP,
edittext(
ids.named_id("ID_TARGET_LINE_4_EDIT_CONTROL"),
context.rect(282, 155, 127, 14),
) + ES_AUTOHSCROLL,
dropdown(
ids.named_id("ID_TARGET_LINE_4_COMBO_BOX_2"),
context.rect(283, 156, 155, 15),
) + WS_VSCROLL
+ WS_TABSTOP,
ltext(
"Parameter",
ids.named_id("ID_TARGET_LINE_4_LABEL_2"),
context.rect(220, 158, 189, 9),
) + NOT_WS_GROUP,
pushbutton(
"Take!",
ids.named_id("ID_TARGET_LINE_4_BUTTON"),
context.rect(412, 154, 26, 14),
),
ltext(
"Hint",
ids.named_id("ID_TARGET_LINE_4_LABEL_3"),
context.rect(412, 158, 26, 9),
) + NOT_WS_GROUP,
ltext(
"Line 5",
ids.named_id("ID_TARGET_LINE_5_LABEL_1"),
context.rect(181, 178, 35, 9),
) + NOT_WS_GROUP,
edittext(
ids.named_id("ID_TARGET_LINE_5_EDIT_CONTROL"),
context.rect(282, 175, 127, 14),
) + ES_AUTOHSCROLL,
context.checkbox(
"Monitoring FX",
ids.named_id("ID_TARGET_CHECK_BOX_1"),
rect(181, 175, 68, 8),
) + WS_TABSTOP,
context.checkbox(
"Track must be selected",
ids.named_id("ID_TARGET_CHECK_BOX_2"),
rect(255, 175, 101, 8),
) + WS_TABSTOP,
context.checkbox(
"FX must have focus",
ids.named_id("ID_TARGET_CHECK_BOX_3"),
rect(363, 175, 76, 8),
) + WS_TABSTOP,
context.checkbox(
"Monitoring FX",
ids.named_id("ID_TARGET_CHECK_BOX_4"),
rect(181, 195, 69, 8),
) + WS_TABSTOP,
context.checkbox(
"Track must be selected",
ids.named_id("ID_TARGET_CHECK_BOX_5"),
rect(255, 195, 101, 8),
) + WS_TABSTOP,
context.checkbox(
"FX must have focus",
ids.named_id("ID_TARGET_CHECK_BOX_6"),
rect(363, 195, 76, 8),
) + WS_TABSTOP,
ltext(
"Value",
ids.named_id("ID_TARGET_VALUE_LABEL_TEXT"),
context.rect(182, 216, 20, 9),
) + NOT_WS_GROUP,
pushbutton(
"Off",
ids.named_id("ID_TARGET_VALUE_OFF_BUTTON"),
context.rect(210, 213, 32, 14),
),
pushbutton(
"On",
ids.named_id("ID_TARGET_VALUE_ON_BUTTON"),
context.rect(250, 213, 32, 14),
),
slider(
ids.named_id("ID_TARGET_VALUE_SLIDER_CONTROL"),
context.rect(215, 213, 74, 15),
) + WS_TABSTOP,
edittext(
ids.named_id("ID_TARGET_VALUE_EDIT_CONTROL"),
context.rect(289, 213, 30, 14),
) + ES_AUTOHSCROLL,
ltext(
"% 1 ms",
ids.named_id("ID_TARGET_VALUE_TEXT"),
context.rect(321, 216, 71, 9),
) + SS_WORDELLIPSIS
+ NOT_WS_GROUP,
pushbutton(
"bpm (bpm)",
ids.named_id("ID_TARGET_UNIT_BUTTON"),
context.rect(393, 213, 40, 14),
),
];
let glue_controls = [
// groupbox(
// "Glue",
// ids.named_id("ID_GLUE_PANEL_LABEL"),
// context.rect(7, 232, 435, 239),
// ),
ltext(
"Glue",
ids.named_id("ID_MAPPING_PANEL_GLUE_LABEL"),
context.rect(7, 232, 435, 9),
),
pushbutton(
"Reset to defaults",
ids.named_id("ID_SETTINGS_RESET_BUTTON"),
context.rect(11, 243, 211, 14),
),
ltext(
"Source",
ids.named_id("ID_SETTINGS_SOURCE_LABEL"),
context.rect(15, 281, 24, 9),
) + NOT_WS_GROUP,
groupbox(
"Source",
ids.named_id("ID_SETTINGS_SOURCE_GROUP"),
context.rect(55, 270, 74, 15),
) + WS_GROUP
+ SkipOnMacOs,
ltext(
"Min",
ids.named_id("ID_SETTINGS_SOURCE_MIN_LABEL"),
context.rect(41, 273, 15, 9),
) + NOT_WS_GROUP,
slider(
ids.named_id("ID_SETTINGS_MIN_SOURCE_VALUE_SLIDER_CONTROL"),
context.rect(55, 270, 74, 15),
) + WS_TABSTOP,
edittext(
ids.named_id("ID_SETTINGS_MIN_SOURCE_VALUE_EDIT_CONTROL"),
context.rect(129, 271, 30, 14),
) + ES_AUTOHSCROLL,
ltext(
"Max",
ids.named_id("ID_SETTINGS_SOURCE_MAX_LABEL"),
context.rect(41, 291, 15, 9),
) + NOT_WS_GROUP,
slider(
ids.named_id("ID_SETTINGS_MAX_SOURCE_VALUE_SLIDER_CONTROL"),
context.rect(55, 288, 74, 15),
) + WS_TABSTOP,
edittext(
ids.named_id("ID_SETTINGS_MAX_SOURCE_VALUE_EDIT_CONTROL"),
context.rect(129, 288, 30, 14),
) + ES_AUTOHSCROLL,
ltext(
"Out-of-range behavior",
ids.named_id("ID_MODE_OUT_OF_RANGE_LABEL_TEXT"),
context.rect(15, 308, 70, 9),
) + NOT_WS_GROUP,
dropdown(
ids.named_id("ID_MODE_OUT_OF_RANGE_COMBOX_BOX"),
context.rect(92, 306, 125, 15),
) + WS_TABSTOP,
ltext(
"Group interaction",
ids.named_id("ID_MODE_GROUP_INTERACTION_LABEL_TEXT"),
context.rect(15, 327, 71, 9),
) + NOT_WS_GROUP,
dropdown(
ids.named_id("ID_MODE_GROUP_INTERACTION_COMBO_BOX"),
context.rect(92, 325, 125, 15),
) + WS_TABSTOP,
ltext(
"Target",
ids.named_id("ID_SETTINGS_TARGET_LABEL_TEXT"),
context.rect(231, 281, 22, 9),
) + NOT_WS_GROUP,
ltext(
"Value sequence",
ids.named_id("ID_SETTINGS_TARGET_SEQUENCE_LABEL_TEXT"),
context.rect(231, 246, 55, 9),
) + NOT_WS_GROUP,
edittext(
ids.named_id("ID_MODE_TARGET_SEQUENCE_EDIT_CONTROL"),
context.rect(288, 243, 149, 14),
) + ES_AUTOHSCROLL,
groupbox(
"Target",
ids.named_id("ID_SETTINGS_TARGET_GROUP"),
context.rect(271, 270, 75, 15),
) + SkipOnMacOs,
ltext(
"Min",
ids.named_id("ID_SETTINGS_MIN_TARGET_LABEL_TEXT"),
context.rect(257, 273, 15, 9),
) + NOT_WS_GROUP,
slider(
ids.named_id("ID_SETTINGS_MIN_TARGET_VALUE_SLIDER_CONTROL"),
context.rect(271, 270, 75, 15),
) + WS_TABSTOP,
edittext(
ids.named_id("ID_SETTINGS_MIN_TARGET_VALUE_EDIT_CONTROL"),
context.rect(347, 270, 30, 14),
) + ES_AUTOHSCROLL,
ltext(
"% 1 ms",
ids.named_id("ID_SETTINGS_MIN_TARGET_VALUE_TEXT"),
context.rect(379, 273, 56, 9),
) + SS_WORDELLIPSIS
+ NOT_WS_GROUP,
ltext(
"Max",
ids.named_id("ID_SETTINGS_MAX_TARGET_LABEL_TEXT"),
context.rect(257, 291, 15, 9),
) + NOT_WS_GROUP,
slider(
ids.named_id("ID_SETTINGS_MAX_TARGET_VALUE_SLIDER_CONTROL"),
context.rect(271, 287, 75, 15),
) + WS_TABSTOP,
edittext(
ids.named_id("ID_SETTINGS_MAX_TARGET_VALUE_EDIT_CONTROL"),
context.rect(347, 288, 30, 14),
) + ES_AUTOHSCROLL,
ltext(
"% 127 ms",
ids.named_id("ID_SETTINGS_MAX_TARGET_VALUE_TEXT"),
context.rect(379, 291, 56, 9),
) + SS_WORDELLIPSIS
+ NOT_WS_GROUP,
context.checkbox(
"Reverse",
ids.named_id("ID_SETTINGS_REVERSE_CHECK_BOX"),
rect(400, 307, 39, 8),
) + WS_TABSTOP,
dropdown(
ids.named_id("IDC_MODE_FEEDBACK_TYPE_COMBO_BOX"),
context.rect(231, 306, 163, 30),
) + WS_TABSTOP,
edittext(
ids.named_id("ID_MODE_EEL_FEEDBACK_TRANSFORMATION_EDIT_CONTROL"),
context.rect(231, 323, 179, 14),
) + ES_AUTOHSCROLL,
pushbutton(
"...",
ids.named_id("IDC_MODE_FEEDBACK_TYPE_BUTTON"),
context.rect(413, 323, 25, 14),
),
groupbox(
"For knobs/faders and buttons (control only)",
ids.named_id("ID_MODE_KNOB_FADER_GROUP_BOX"),
context.rect(11, 344, 211, 123),
),
ltext(
"Mode",
ids.named_id("ID_SETTINGS_MODE_LABEL"),
context.rect(15, 357, 20, 9),
) + NOT_WS_GROUP,
dropdown(
ids.named_id("ID_SETTINGS_MODE_COMBO_BOX"),
context.rect(50, 355, 168, 15),
) + WS_TABSTOP,
ltext(
"Takeover",
ids.named_id("ID_MODE_TAKEOVER_LABEL"),
context.rect(15, 409, 35, 9),
) + NOT_WS_GROUP,
dropdown(
ids.named_id("ID_MODE_TAKEOVER_MODE"),
context.rect(53, 407, 86, 15),
) + WS_TABSTOP,
context.checkbox(
"Round target value",
ids.named_id("ID_SETTINGS_ROUND_TARGET_VALUE_CHECK_BOX"),
rect(146, 409, 73, 8),
) + WS_TABSTOP,
ltext(
"Control transformation (EEL)",
ids.named_id("ID_MODE_EEL_CONTROL_TRANSFORMATION_LABEL"),
context.rect(15, 423, 95, 9),
) + NOT_WS_GROUP,
edittext(
ids.named_id("ID_MODE_EEL_CONTROL_TRANSFORMATION_EDIT_CONTROL"),
context.rect(15, 435, 184, 14),
) + ES_AUTOHSCROLL,
pushbutton(
"...",
ids.named_id("ID_MODE_EEL_CONTROL_TRANSFORMATION_DETAIL_BUTTON"),
context.rect(201, 435, 13, 14),
),
groupbox(
"For encoders and incremental buttons (control only)",
ids.named_id("ID_MODE_RELATIVE_GROUP_BOX"),
context.rect(227, 344, 211, 61),
),
ltext(
"Step size",
ids.named_id("ID_SETTINGS_STEP_SIZE_LABEL_TEXT"),
context.rect(231, 366, 30, 9),
) + NOT_WS_GROUP,
groupbox(
"Step size",
ids.named_id("ID_SETTINGS_STEP_SIZE_GROUP"),
context.rect(279, 355, 74, 15),
) + SkipOnMacOs,
ltext(
"Min",
ids.named_id("ID_SETTINGS_MIN_STEP_SIZE_LABEL_TEXT"),
context.rect(266, 358, 15, 9),
) + NOT_WS_GROUP,
slider(
ids.named_id("ID_SETTINGS_MIN_STEP_SIZE_SLIDER_CONTROL"),
context.rect(279, 355, 74, 15),
) + WS_TABSTOP,
edittext(
ids.named_id("ID_SETTINGS_MIN_STEP_SIZE_EDIT_CONTROL"),
context.rect(353, 355, 30, 14),
) + ES_AUTOHSCROLL,
ltext(
"% 1 ms",
ids.named_id("ID_SETTINGS_MIN_STEP_SIZE_VALUE_TEXT"),
context.rect(385, 358, 51, 9),
) + SS_WORDELLIPSIS
+ NOT_WS_GROUP,
ltext(
"Max",
ids.named_id("ID_SETTINGS_MAX_STEP_SIZE_LABEL_TEXT"),
context.rect(266, 376, 15, 9),
) + NOT_WS_GROUP,
slider(
ids.named_id("ID_SETTINGS_MAX_STEP_SIZE_SLIDER_CONTROL"),
context.rect(279, 372, 74, 15),
) + WS_TABSTOP,
edittext(
ids.named_id("ID_SETTINGS_MAX_STEP_SIZE_EDIT_CONTROL"),
context.rect(353, 372, 30, 14),
) + ES_AUTOHSCROLL,
ltext(
"% 127 ms",
ids.named_id("ID_SETTINGS_MAX_STEP_SIZE_VALUE_TEXT"),
context.rect(385, 375, 51, 9),
) + SS_WORDELLIPSIS
+ NOT_WS_GROUP,
dropdown(
ids.named_id("ID_MODE_RELATIVE_FILTER_COMBO_BOX"),
context.rect(231, 388, 104, 15),
) + WS_TABSTOP,
context.checkbox(
"Wrap",
ids.named_id("ID_SETTINGS_ROTATE_CHECK_BOX"),
rect(342, 391, 30, 8),
) + WS_TABSTOP,
context.checkbox(
"Make absolute",
ids.named_id("ID_SETTINGS_MAKE_ABSOLUTE_CHECK_BOX"),
rect(375, 391, 60, 8),
) + WS_TABSTOP,
groupbox(
"For buttons (control only)",
ids.named_id("ID_MODE_BUTTON_GROUP_BOX"),
context.rect(227, 406, 211, 61),
),
dropdown(
ids.named_id("ID_MODE_FIRE_COMBO_BOX"),
context.rect(231, 416, 131, 15),
) + WS_TABSTOP,
dropdown(
ids.named_id("ID_MODE_BUTTON_FILTER_COMBO_BOX"),
context.rect(367, 416, 68, 15),
) + WS_TABSTOP,
ltext(
"Min",
ids.named_id("ID_MODE_FIRE_LINE_2_LABEL_1"),
context.rect(231, 436, 30, 9),
) + NOT_WS_GROUP,
slider(
ids.named_id("ID_MODE_FIRE_LINE_2_SLIDER_CONTROL"),
context.rect(265, 432, 87, 15),
) + WS_TABSTOP,
edittext(
ids.named_id("ID_MODE_FIRE_LINE_2_EDIT_CONTROL"),
context.rect(353, 432, 30, 14),
) + ES_AUTOHSCROLL,
ltext(
"% 1 ms",
ids.named_id("ID_MODE_FIRE_LINE_2_LABEL_2"),
context.rect(385, 435, 50, 9),
) + SS_WORDELLIPSIS
+ NOT_WS_GROUP,
ltext(
"Max",
ids.named_id("ID_MODE_FIRE_LINE_3_LABEL_1"),
context.rect(231, 454, 31, 9),
) + NOT_WS_GROUP,
slider(
ids.named_id("ID_MODE_FIRE_LINE_3_SLIDER_CONTROL"),
context.rect(265, 449, 87, 15),
) + WS_TABSTOP,
edittext(
ids.named_id("ID_MODE_FIRE_LINE_3_EDIT_CONTROL"),
context.rect(353, 449, 30, 14),
) + ES_AUTOHSCROLL,
ltext(
"% 127 ms",
ids.named_id("ID_MODE_FIRE_LINE_3_LABEL_2"),
context.rect(385, 452, 50, 9),
) + SS_WORDELLIPSIS
+ NOT_WS_GROUP,
];
let footer_controls = [
ltext(
"Help left",
ids.named_id("ID_MAPPING_HELP_LEFT_SUBJECT_LABEL"),
context.rect(7, 475, 183, 9),
) + NOT_WS_GROUP,
edittext(
ids.named_id("ID_MAPPING_HELP_LEFT_CONTENT_LABEL"),
context.rect(7, 488, 210, 22),
) + ES_MULTILINE
+ ES_READONLY
+ WS_VSCROLL,
static_text(
"",
ids.named_id("IDC_MAPPING_MATCHED_INDICATOR_TEXT"),
context.rect(220, 495, 8, 8),
) + SS_LEFTNOWORDWRAP
+ WS_DISABLED
+ WS_GROUP
+ WS_TABSTOP,
ltext(
"Help right",
ids.named_id("ID_MAPPING_HELP_RIGHT_SUBJECT_LABEL"),
context.rect(7 + 225, 475, 183, 9),
) + NOT_WS_GROUP,
edittext(
ids.named_id("ID_MAPPING_HELP_RIGHT_CONTENT_LABEL"),
context.rect(7 + 225, 488, 210, 22),
) + ES_MULTILINE
+ ES_READONLY
+ WS_VSCROLL,
context.checkbox(
"Beep on success",
ids.named_id("IDC_BEEP_ON_SUCCESS_CHECK_BOX"),
rect(7, 516, 70, 10),
) + WS_TABSTOP,
pushbutton(
"<=",
ids.named_id("ID_MAPPING_PANEL_PREVIOUS_BUTTON"),
context.rect(160, 514, 30, 14),
),
// We make this a normal push button instead of an OK button so that
// pressing enter in a text field doesn't automatically trigger it.
pushbutton(
"OK",
ids.named_id("ID_MAPPING_PANEL_OK"),
context.rect(200, 514, 50, 14),
),
pushbutton(
"=>",
ids.named_id("ID_MAPPING_PANEL_NEXT_BUTTON"),
context.rect(260, 514, 30, 14),
),
context.checkbox(
"Enabled",
ids.named_id("IDC_MAPPING_ENABLED_CHECK_BOX"),
rect(405, 516, 39, 10),
) + WS_TABSTOP,
];
Dialog {
id: ids.named_id("ID_MAPPING_PANEL"),
caption: "Edit mapping",
kind: DialogKind::DIALOGEX,
rect: context.rect(0, 0, 451, 532),
styles: Styles(vec![
DS_SETFONT,
DS_MODALFRAME,
DS_3DLOOK,
DS_CENTER,
WS_POPUP,
WS_VISIBLE,
WS_CAPTION,
WS_SYSMENU,
]),
controls: mapping_controls
.into_iter()
.chain(source_controls)
.chain(target_controls)
.chain(glue_controls)
.chain(footer_controls)
.collect(),
..context.default_dialog()
}
}
@@ -0,0 +1,101 @@
use crate::base::Condition::SkipOnMacOs;
use crate::base::*;
use crate::constants::{MAPPING_ROW_PANEL_HEIGHT, MAPPING_ROW_PANEL_WIDTH};
pub fn create(context: ScopedContext, ids: &mut IdGenerator) -> Dialog {
use Style::*;
let controls = [
// Label and on/off checkbox
ltext(
"Mapping 1",
ids.named_id("ID_MAPPING_ROW_MAPPING_LABEL"),
context.rect(14, 0, 225, 9),
) + NOT_WS_GROUP,
context.checkbox(
"",
ids.named_id("IDC_MAPPING_ROW_ENABLED_CHECK_BOX"),
rect(2, 0, 10, 10),
) + WS_GROUP,
// Mapping actions
pushbutton(
"Edit",
ids.named_id("ID_MAPPING_ROW_EDIT_BUTTON"),
context.rect(347, 13, 31, 14),
) + NOT_WS_TABSTOP,
pushbutton(
"Duplicate",
ids.named_id("ID_MAPPING_ROW_DUPLICATE_BUTTON"),
context.rect(379, 13, 37, 14),
) + NOT_WS_TABSTOP,
pushbutton(
"Remove",
ids.named_id("ID_MAPPING_ROW_REMOVE_BUTTON"),
context.rect(417, 13, 31, 14),
) + NOT_WS_TABSTOP,
pushbutton(
"Learn source",
ids.named_id("ID_MAPPING_ROW_LEARN_SOURCE_BUTTON"),
context.rect(347, 28, 47, 14),
) + WS_GROUP
+ NOT_WS_TABSTOP,
pushbutton(
"Learn target",
ids.named_id("ID_MAPPING_ROW_LEARN_TARGET_BUTTON"),
context.rect(395, 28, 53, 14),
) + NOT_WS_TABSTOP,
// Control/feedback checkboxes
context.checkbox(
"=>",
ids.named_id("ID_MAPPING_ROW_CONTROL_CHECK_BOX"),
rect(140, 15, 24, 8),
),
context.checkbox(
"<=",
ids.named_id("ID_MAPPING_ROW_FEEDBACK_CHECK_BOX"),
rect(140, 30, 24, 8),
),
// Source and target labels
ctext(
"MIDI CC Value (ch1, cc5)\r\nbla\r\nbla",
ids.named_id("ID_MAPPING_ROW_SOURCE_LABEL_TEXT"),
context.rect(43, 12, 94, 34),
) + NOT_WS_GROUP,
ctext(
"FX Param Target\r\nbla\r\nbla\r\nmoin",
ids.named_id("ID_MAPPING_ROW_TARGET_LABEL_TEXT"),
context.rect(164, 12, 179, 34),
) + NOT_WS_GROUP,
// Group label
rtext(
"Group 1",
ids.named_id("ID_MAPPING_ROW_GROUP_LABEL"),
context.rect(239, 0, 208, 9),
) + NOT_WS_GROUP,
// Match indicator
ltext(
"",
ids.named_id("IDC_MAPPING_ROW_MATCHED_INDICATOR_TEXT"),
context.rect(3, 23, 8, 8),
) + WS_DISABLED,
// Up/down buttons
groupbox("Up", ids.id(), context.rect(13, 13, 26, 14)) + WS_GROUP + SkipOnMacOs,
pushbutton(
"Up",
ids.named_id("ID_UP_BUTTON"),
context.rect(13, 13, 26, 14),
),
pushbutton(
"Down",
ids.named_id("ID_DOWN_BUTTON"),
context.rect(13, 28, 26, 14),
),
];
Dialog {
id: ids.named_id("ID_MAPPING_ROW_PANEL"),
kind: DialogKind::DIALOGEX,
rect: context.rect(0, 0, MAPPING_ROW_PANEL_WIDTH, MAPPING_ROW_PANEL_HEIGHT),
styles: Styles(vec![DS_SETFONT, DS_CONTROL, WS_CHILD]),
controls: controls.into_iter().collect(),
..context.default_dialog()
}
}
@@ -0,0 +1,26 @@
use crate::base::*;
use crate::constants::{MAPPING_ROWS_PANEL_HEIGHT, MAPPING_ROWS_PANEL_WIDTH};
pub fn create(context: ScopedContext, ids: &mut IdGenerator) -> Dialog {
use Style::*;
let controls = [
pushbutton(
"Display mappings in all groups",
ids.named_id("ID_DISPLAY_ALL_GROUPS_BUTTON"),
context.rect(157, 137, 156, 14),
),
ctext(
"There are no mappings in this compartment.",
ids.named_id("ID_GROUP_IS_EMPTY_TEXT"),
context.rect(149, 121, 173, 9),
) + NOT_WS_GROUP,
];
Dialog {
id: ids.named_id("ID_MAPPING_ROWS_PANEL"),
kind: DialogKind::DIALOGEX,
rect: context.rect(0, 0, MAPPING_ROWS_PANEL_WIDTH, MAPPING_ROWS_PANEL_HEIGHT),
styles: Styles(vec![DS_SETFONT, DS_CONTROL, WS_CHILD, WS_VISIBLE]),
controls: controls.into_iter().collect(),
..context.default_dialog()
}
}
@@ -0,0 +1,31 @@
use crate::base::*;
pub fn create(context: ScopedContext, ids: &mut IdGenerator) -> Dialog {
use Style::*;
let controls = [ctext(
"Some message",
ids.named_id("ID_MESSAGE_TEXT"),
context.rect(0, 0, 300, 40),
) + SS_CENTERIMAGE
+ SS_WORDELLIPSIS
+ NOT_WS_GROUP];
Dialog {
id: ids.named_id("ID_MESSAGE_PANEL"),
caption: "Helgobox",
rect: context.rect(0, 0, 300, 40),
styles: Styles(vec![
DS_SETFONT,
DS_MODALFRAME,
DS_3DLOOK,
DS_FIXEDSYS,
DS_CENTER,
WS_POPUP,
WS_VISIBLE,
WS_CAPTION,
WS_SYSMENU,
]),
ex_styles: Styles(vec![WS_EX_TOPMOST, WS_EX_WINDOWEDGE]),
controls: controls.into_iter().collect(),
..context.default_dialog()
}
}
@@ -0,0 +1,2 @@
#endif // German (Germany) resources
/////////////////////////////////////////////////////////////////////////////
@@ -0,0 +1,7 @@
// Resource script generated by helgobox-dialogs.
//
#include "resource.h"
#include "windows.h"
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_DEU)
LANGUAGE LANG_GERMAN, SUBLANG_GERMAN
@@ -0,0 +1,98 @@
use crate::base::*;
use crate::ext::*;
pub fn create(context: ScopedContext, ids: &mut IdGenerator) -> Dialog {
use Style::*;
let col_1_x = 0;
let line_1_y = 0;
let line_2_y = line_1_y + 20;
let controls = vec![
// Name
ltext(
"Name",
ids.named_id("ID_MAPPING_NAME_LABEL"),
context.rect(col_1_x, line_1_y + 3, 20, 9),
) + NOT_WS_GROUP,
edittext(
ids.named_id("ID_MAPPING_NAME_EDIT_CONTROL"),
context.rect(col_1_x + 28, line_1_y, 131, 14),
) + ES_AUTOHSCROLL,
// Tags
ltext(
"Tags",
ids.named_id("ID_MAPPING_TAGS_LABEL"),
context.rect(col_1_x + 167, line_1_y + 3, 18, 9),
) + NOT_WS_GROUP,
edittext(
ids.named_id("ID_MAPPING_TAGS_EDIT_CONTROL"),
context.rect(col_1_x + 189, line_1_y, 131, 14),
) + ES_AUTOHSCROLL,
// Control/feedback checkboxes
context.checkbox(
"=> Control",
ids.named_id("ID_MAPPING_CONTROL_ENABLED_CHECK_BOX"),
rect(col_1_x + 325, line_1_y + 3, 50, 8),
) + WS_TABSTOP,
context.checkbox(
"<= Feedback",
ids.named_id("ID_MAPPING_FEEDBACK_ENABLED_CHECK_BOX"),
rect(col_1_x + 376, line_1_y + 3, 56, 8),
) + WS_TABSTOP,
// Conditional activation
ltext(
"Active",
ids.named_id("ID_MAPPING_ACTIVATION_TYPE_LABEL"),
context.rect(col_1_x, line_2_y + 2, 21, 9),
) + NOT_WS_GROUP,
dropdown(
ids.named_id("ID_MAPPING_ACTIVATION_TYPE_COMBO_BOX"),
context.rect(col_1_x + 28, line_2_y, 102, 15),
) + WS_TABSTOP,
// Conditional activation criteria 1
ltext(
"Modifier 1",
ids.named_id("ID_MAPPING_ACTIVATION_SETTING_1_LABEL_TEXT"),
context.rect(col_1_x + 138, line_2_y + 2, 34, 9),
) + NOT_WS_GROUP,
pushbutton(
"Pick 1",
ids.named_id("ID_MAPPING_ACTIVATION_SETTING_1_BUTTON"),
context.rect(col_1_x + 177, line_2_y, 90, 15),
) + WS_TABSTOP,
context.checkbox(
"",
ids.named_id("ID_MAPPING_ACTIVATION_SETTING_1_CHECK_BOX"),
rect(col_1_x + 269, line_2_y + 2, 11, 8),
) + WS_TABSTOP,
// Conditional activation criteria 2
ltext(
"Modifier 2",
ids.named_id("ID_MAPPING_ACTIVATION_SETTING_2_LABEL_TEXT"),
context.rect(col_1_x + 287, line_2_y + 2, 34, 9),
) + NOT_WS_GROUP,
pushbutton(
"Pick 1",
ids.named_id("ID_MAPPING_ACTIVATION_SETTING_2_BUTTON"),
context.rect(col_1_x + 325, line_2_y, 90, 15),
) + WS_TABSTOP,
context.checkbox(
"",
ids.named_id("ID_MAPPING_ACTIVATION_SETTING_2_CHECK_BOX"),
rect(col_1_x + 417, line_2_y + 2, 11, 8),
) + WS_TABSTOP,
edittext(
ids.named_id("ID_MAPPING_ACTIVATION_EDIT_CONTROL"),
context.rect(col_1_x + 325, line_2_y, 90, 14),
) + ES_AUTOHSCROLL,
];
Dialog {
id: ids.named_id("ID_SHARED_GROUP_MAPPING_PANEL"),
kind: DialogKind::DIALOGEX,
rect: context.rect(0, 0, 440, 37),
styles: Styles(vec![
DS_SETFONT, DS_CONTROL, DS_CENTER, WS_CHILD, WS_VISIBLE, WS_SYSMENU,
]),
controls,
..context.default_dialog()
}
}
@@ -0,0 +1,47 @@
use crate::base::Condition::SkipOnMacOs;
use crate::base::*;
pub fn create(context: ScopedContext, ids: &mut IdGenerator) -> Dialog {
use Style::*;
let controls = vec![
pushbutton(
"Open in text editor",
ids.named_id("ID_YAML_TEXT_EDITOR_BUTTON"),
context.rect(371, 291, 68, 14),
) + SkipOnMacOs,
edittext(
ids.named_id("ID_YAML_EDIT_CONTROL"),
context.rect(0, 0, 490, 284),
) + ES_MULTILINE
+ ES_WANTRETURN
+ WS_VSCROLL,
pushbutton(
"Help",
ids.named_id("ID_YAML_HELP_BUTTON"),
context.rect(445, 291, 40, 14),
),
ltext(
"",
ids.named_id("ID_YAML_EDIT_INFO_TEXT"),
context.rect(5, 294, 355, 9),
) + NOT_WS_GROUP,
];
Dialog {
id: ids.named_id("ID_YAML_EDITOR_PANEL"),
caption: "Editor",
rect: context.rect(0, 0, 490, 310),
styles: Styles(vec![
DS_SETFONT,
DS_MODALFRAME,
DS_3DLOOK,
DS_FIXEDSYS,
DS_CENTER,
WS_POPUP,
WS_VISIBLE,
WS_CAPTION,
WS_SYSMENU,
]),
controls,
..context.default_dialog()
}
}
@@ -0,0 +1,72 @@
use crate::base::*;
use crate::constants::{FOOTER_PANEL_HEIGHT, MAIN_PANEL_WIDTH};
use crate::ext::divider;
pub fn create(
context: ScopedContext,
ids: &mut IdGenerator,
effective_header_panel_height: u32,
effective_rows_panel_height: u32,
) -> Dialog {
use Style::*;
let footer_y_offset = effective_header_panel_height + effective_rows_panel_height;
let create_rect = |x, y, width, height| {
let local_rect = context.rect(x, y, width, height);
Rect {
y: footer_y_offset + local_rect.y,
..local_rect
}
};
let line_spacing = 12;
let text_line_left = 84;
let text_line_right = MAIN_PANEL_WIDTH - text_line_left;
let text_line_width = text_line_right - text_line_left;
let controls = vec![
divider(ids.id(), create_rect(0, 0, MAIN_PANEL_WIDTH, 1)),
ctext(
"Status 1",
ids.named_id("ID_MAIN_PANEL_STATUS_1_TEXT"),
create_rect(text_line_left, 5, text_line_width, 9),
) + NOT_WS_GROUP,
ctext(
"Status 2",
ids.named_id("ID_MAIN_PANEL_STATUS_2_TEXT"),
create_rect(text_line_left, 5 + line_spacing, text_line_width, 9),
) + NOT_WS_GROUP,
context.checkbox(
"",
ids.named_id("IDC_UNIT_ENABLED_CHECK_BOX"),
create_rect(2, 7 + line_spacing, 10, 14),
) + WS_TABSTOP,
pushbutton(
"Unit",
ids.named_id("IDC_UNIT_BUTTON"),
create_rect(17, 5 + line_spacing, 65, 14),
),
pushbutton(
"Unit data...",
ids.named_id("IDC_EDIT_TAGS_BUTTON"),
create_rect(386, 5 + line_spacing, 76, 14),
),
ctext(
"Helgobox",
ids.named_id("ID_MAIN_PANEL_VERSION_TEXT"),
create_rect(text_line_left, 5 + line_spacing * 2, text_line_width, 9),
),
];
Dialog {
id: ids.named_id("ID_MAIN_PANEL"),
kind: DialogKind::DIALOGEX,
rect: Rect::new(
0,
0,
context.scale_width(MAIN_PANEL_WIDTH),
effective_header_panel_height
+ effective_rows_panel_height
+ context.scale_height(FOOTER_PANEL_HEIGHT),
),
styles: Styles(vec![DS_SETFONT, DS_CONTROL, WS_CHILD, WS_VISIBLE]),
controls,
..context.default_dialog()
}
}
@@ -0,0 +1,64 @@
use crate::base::*;
use crate::ext::ok_button;
pub fn create(context: ScopedContext, ids: &mut IdGenerator) -> Dialog {
use Style::*;
Dialog {
id: ids.named_id("ID_SETUP_PANEL"),
optional: true,
caption: "Welcome to Helgobox!",
rect: context.rect(0, 0, 250, 280),
styles: Styles(vec![
// Places the window into the center by default
DS_CENTER, // Displays a close button
WS_SYSMENU,
]),
controls: vec![
ctext(
"Intro text 1",
ids.named_id("ID_SETUP_INTRO_TEXT_1"),
context.rect(25, 25, 200, 50),
),
ctext(
"Intro text 2",
ids.named_id("ID_SETUP_INTRO_TEXT_2"),
context.rect(25, 80, 200, 30),
),
context.checkbox(
"Add Playtime button to main toolbar",
ids.named_id("ID_SETUP_ADD_PLAYTIME_TOOLBAR_BUTTON"),
context.rect(60, 120, 150, 8),
),
context.checkbox(
"Send errors to developer automatically",
ids.named_id("ID_SETUP_SEND_ERRORS_TO_DEV"),
context.rect(60, 135, 150, 8),
),
context.checkbox(
"Show errors in console",
ids.named_id("ID_SETUP_SHOW_ERRORS_IN_CONSOLE"),
context.rect(60, 150, 150, 8),
),
context.checkbox(
"Notify about updates",
ids.named_id("ID_SETUP_NOTIFY_ABOUT_UPDATES"),
context.rect(60, 165, 150, 8),
),
ctext(
"Comment",
ids.named_id("ID_SETUP_COMMENT"),
context.rect(25, 185, 200, 25),
),
ctext(
"Tip",
ids.named_id("ID_SETUP_TIP_TEXT"),
context.rect(25, 210, 200, 25),
),
ok_button(
ids.named_id("ID_SETUP_PANEL_OK"),
context.rect(75, 240, 100, 14),
),
],
..context.default_dialog()
}
}