Skip to content

Commit

Permalink
#593 Multiple changes
Browse files Browse the repository at this point in the history
- Fix SWELL dialog generation on macOS and Linux
- Don't require building with "generate" feature anymore after
  changing dialogs
  • Loading branch information
helgoboss committed Jun 11, 2022
1 parent c00d460 commit 24e3053
Show file tree
Hide file tree
Showing 15 changed files with 449 additions and 131 deletions.
21 changes: 6 additions & 15 deletions CONTRIBUTING.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -140,34 +140,25 @@ cargo build

=== All operating systems

Whenever you adjust code in the `dialogs` directory, you actively need to run a build with the feature `generate` enabled. As the name implies, this will regenerate a bunch of code which is part of the source code repository:

- `main/src/infrastructure/ui/bindings.rc` (Rust bindings to dialog resource ID constants)
- `main/src/infrastructure/ui/realearn.rc_mac_dlg` (macOS and Linux only)

Here's how you do it in detail:
If you update Cockos WDL, you should also regenerate Rust bindings (we use WDL's EEL):

[source,sh]
----
cargo build --features generate
cargo fmt
----

[IMPORTANT]
====
If you don't do this, your `dialogs` changes will not have an effect on the macOS and Linux GUI!
The final `generate` build before publishing your `dialogs` changes **needs to be done on Linux** (or Windows WSL)! Otherwise it's possible that the result works on macOS and Windows, but not on Linux.
====

== GUI

The GUI dialogs are defined in the `dialogs` directory. Whenever ReaLearn is built, the code there generates an old-school Windows dialog resource file in link:main/src/infrastructure/ui/msvc/msvc.rc[msvc.rc].
The GUI dialogs are defined in the `dialogs` directory. Whenever ReaLearn is built, the code there generates an old-school Windows dialog resource file in link:main/src/infrastructure/ui/msvc/msvc.rc[msvc.rc], a Rust file which contains all the resource ID constants and a corresponding C header file (in case you want to preview the dialog in a visual resource editor).

Previously I used the Visual C++ 2019 resource editor to WYSIWYG-edit this file as part of the solution
link:main/src/infrastructure/ui/msvc/msvc.sln[msvc.sln], but this was too tedious.

WARNING: Don't edit the rc file, the changes will be overwritten at build time! Adjust the Rust code in the `dialogs` directory instead.
WARNING: Don't edit the RC file, the changes will be overwritten at build time! Adjust the Rust code in the `dialogs` directory instead.

On macOS and Linux, an extra step will happen at build time: It will try to use a PHP script (part of Cockos SWELL) to generate
`main/src/infrastructure/ui/msvc/msvc.rc_mac_dlg`, which is a translation of the RC file to C code using SWELL. So make sure you have PHP installed on these platforms!

== Test

Expand Down
10 changes: 0 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 34 additions & 8 deletions dialogs/src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,57 @@ use std::ops::Add;

pub type Caption = &'static str;

pub struct ResourceHeader {
pub struct ResourceInfo {
y_scale: f64,
height_scale: f64,
named_ids: Vec<Id>,
}

impl Display for ResourceHeader {
/// 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<'a> Display for ResourceInfoAsCHeaderCode<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
writeln!(f, "#define Y_SCALE {:.4}", self.y_scale)?;
writeln!(f, "#define HEIGHT_SCALE {:.4}", self.height_scale)?;
for id in &self.named_ids {
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<'a> Display for ResourceInfoAsRustCode<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("pub mod root {\n")?;
writeln!(f, " pub const Y_SCALE: f64 = {:.4};", self.0.y_scale)?;
writeln!(
f,
" pub const HEIGHT_SCALE: f64 = {:.4};",
self.0.height_scale
)?;
for id in &self.0.named_ids {
writeln!(f, " pub const {}: u32 = {};", id.name, id.value)?;
}
f.write_str("}\n")?;
Ok(())
}
}

#[derive(Default)]
pub struct Resource {
pub dialogs: Vec<Dialog>,
}

impl Resource {
pub fn generate_header(&self, context: &Context) -> ResourceHeader {
ResourceHeader {
pub fn generate_info(&self, context: &Context) -> ResourceInfo {
ResourceInfo {
y_scale: context.y_scale,
height_scale: context.height_scale,
named_ids: self.named_ids().collect(),
Expand Down Expand Up @@ -346,7 +372,7 @@ pub struct Rect {

impl Display for Rect {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{},{},{},{}", self.x, self.y, self.width, self.height)
write!(f, "{}, {}, {}, {}", self.x, self.y, self.width, self.height)
}
}

Expand Down
27 changes: 20 additions & 7 deletions dialogs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::base::{Context, Dialog, Font, Resource};
use crate::base::{
Context, Dialog, Font, Resource, ResourceInfoAsCHeaderCode, ResourceInfoAsRustCode,
};
use std::io::Write;
use std::path::Path;

mod base;
Expand Down Expand Up @@ -43,14 +46,24 @@ pub fn generate_dialog_files(out_dir: impl AsRef<Path>) {
yaml_editor_panel::create(&mut context),
],
};
// Write header file
let header_file_content = resource.generate_header(&context).to_string();
std::fs::write(out_dir.as_ref().join("resource.h"), header_file_content)
.expect("couldn't write header file");
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(out_dir.as_ref().join("msvc/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(out_dir.as_ref().join("bindings.rs"), 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!("{}\n\n{}\n\n{}", rc_file_header, resource, rc_file_footer);
std::fs::write(out_dir.as_ref().join("msvc.rc"), rc_file_content)
.expect("couldn't write rc file");
let mut output = Vec::new();
// Write UTF_16LE BOM
output.write_all(&[0xFF, 0xFE]).unwrap();
for utf16 in rc_file_content.encode_utf16() {
output.write_all(&utf16.to_le_bytes()).unwrap();
}
std::fs::write(out_dir.as_ref().join("msvc/msvc.rc"), output).expect("couldn't write rc file");
}
4 changes: 1 addition & 3 deletions main/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,8 @@ built = { version = "0.5.1", features = ["git2", "chrono"] }
bindgen = "0.52.0"
# For compiling EEL and SWELL dialog resources
cc = { git = "https://github.com/petrochenkov/cc-rs.git", rev = "9efa25c77c50229fa84fb09f59c034417d816aa8" }
# For embedding ResEdit files on Windows
# For embedding dialog resource files on Windows
embed-resource = "1.3"
# For being able to read the UTF-16LE dialog resource file generated by MSVC
encoding_rs = "0.8.28"
# For RC dialog file generation
realearn-dialogs = { path = "../dialogs" }

Expand Down
72 changes: 23 additions & 49 deletions main/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ fn main() -> Result<(), Box<dyn Error>> {
built::write_built_file().expect("Failed to acquire build-time information");

// Generate GUI dialog files (rc file and C header)
realearn_dialogs::generate_dialog_files("src/infrastructure/ui/msvc");
realearn_dialogs::generate_dialog_files("src/infrastructure/ui");

// Optionally generate bindings, on macOS and Linux also SWELL dialogs.
// On macOS and Linux, try to generate SWELL dialogs (needs PHP)
#[cfg(target_family = "unix")]
if let Err(e) = generate_dialogs() {
println!("cargo:warning={}", e);
}

// Optionally generate bindings (e.g. from Cockos EEL)
#[cfg(feature = "generate")]
codegen::generate_bindings();
#[cfg(all(feature = "generate", target_family = "unix"))]
codegen::generate_dialogs();

// Embed or compile dialogs
#[cfg(target_family = "windows")]
Expand Down Expand Up @@ -113,42 +117,11 @@ fn embed_dialog_resources() {
#[cfg(feature = "generate")]
mod codegen {
use crate::util;
use std::error::Error;

/// Generates Rust bindings for a couple of C stuff.
pub fn generate_bindings() {
generate_core_bindings();
generate_infrastructure_bindings();
}

/// Generates dialog window C++ code from resource file using SWELL's PHP-based dialog generator
/// (too obscure to be ported to Rust).
#[cfg(target_family = "unix")]
pub fn generate_dialogs() {
use std::io::Read;
// Make RC file SWELL-compatible.
// ResEdit uses WS_CHILDWINDOW but SWELL understands WS_CHILD only. Rename it.
let mut rc_file = std::fs::File::open("src/infrastructure/ui/msvc/msvc.rc")
.expect("couldn't find msvc.rc");
let mut rc_buf = vec![];
rc_file
.read_to_end(&mut rc_buf)
.expect("couldn't read msvc.rc");
let (original_rc_content, ..) = encoding_rs::UTF_16LE.decode(&rc_buf);
let modified_rc_content = original_rc_content.replace("WS_CHILDWINDOW", "WS_CHILD");
std::fs::write("../target/realearn.modified.rc", modified_rc_content)
.expect("couldn't write modified RC file");
// Use PHP to translate SWELL-compatible RC file to C++
let result = std::process::Command::new("php")
.arg("lib/WDL/WDL/swell/swell_resgen.php")
.arg("../target/realearn.modified.rc")
.output()
.expect("PHP dialog translator result not available");
std::fs::copy(
"../target/realearn.modified.rc_mac_dlg",
"src/infrastructure/ui/realearn.rc_mac_dlg",
)
.unwrap();
assert!(result.status.success(), "PHP dialog generation failed");
}

fn generate_core_bindings() {
Expand All @@ -174,21 +147,22 @@ mod codegen {
.write_to_file(out_path.join("src/base/bindings.rs"))
.expect("Couldn't write bindings!");
}
}

fn generate_infrastructure_bindings() {
// Tell cargo to invalidate the built crate whenever the wrapper changes
println!("cargo:rerun-if-changed=src/infrastructure/ui/wrapper.hpp");
let bindings = bindgen::Builder::default()
.header("src/infrastructure/ui/wrapper.hpp")
.enable_cxx_namespaces()
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
.generate()
.expect("Unable to generate bindings");
let out_path = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
bindings
.write_to_file(out_path.join("src/infrastructure/ui/bindings.rs"))
.expect("Couldn't write bindings!");
/// Generates dialog window C++ code from resource file using SWELL's PHP-based dialog generator
/// (too obscure to be ported to Rust).
#[cfg(target_family = "unix")]
pub fn generate_dialogs() -> Result<(), Box<dyn Error>> {
// Use PHP to translate SWELL-compatible RC file to C++
let result = std::process::Command::new("php")
.arg("lib/WDL/WDL/swell/swell_resgen.php")
.arg("src/infrastructure/ui/msvc/msvc.rc")
.output()
.expect("PHP dialog translator result not available");
if !result.status.success() {
return Err("PHP dialog generation failed (PHP not installed or script failed)".into());
}
Ok(())
}

mod util {
Expand Down
9 changes: 2 additions & 7 deletions main/src/infrastructure/ui/bindings.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
/* automatically generated by rust-bindgen */

#[allow(non_snake_case, non_camel_case_types, non_upper_case_globals)]
pub mod root {
#[allow(unused_imports)]
use self::super::root;
pub const Y_SCALE: f64 = 1.0;
pub const HEIGHT_SCALE: f64 = 1.0;
pub const Y_SCALE: f64 = 1.0000;
pub const HEIGHT_SCALE: f64 = 1.0000;
pub const ID_GROUP_PANEL: u32 = 30000;
pub const ID_GROUP_PANEL_OK: u32 = 30001;
pub const ID_HEADER_PANEL: u32 = 30040;
Expand Down
4 changes: 2 additions & 2 deletions main/src/infrastructure/ui/dialogs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#define SWELL_PROVIDED_BY_APP

// Some preparation for dialog generation.
#include "msvc/Resource.h"
#include "msvc/resource.h"
#include "../../../lib/WDL/WDL/swell/swell.h"
// Make sure the following factors correspond to the ones in `units.rs` (function `effective_scale_factors`).
#ifdef __APPLE__
Expand All @@ -23,4 +23,4 @@
#define SS_WORDELLIPSIS 0

// This is the result of the dialog RC file conversion (via PHP script).
#include "realearn.rc_mac_dlg"
#include "msvc/msvc.rc_mac_dlg"
Binary file modified main/src/infrastructure/ui/msvc/msvc.rc
Binary file not shown.
Loading

0 comments on commit 24e3053

Please sign in to comment.