From 7878215c195395aa0056764a0297877272fbf4ff Mon Sep 17 00:00:00 2001 From: Stu Black Date: Sun, 5 Apr 2026 12:57:39 -0400 Subject: [PATCH 1/9] Fix false positive improper_ctypes warnings for structs that are opaque types from C. --- WINGs/wings-rs/src/lib.rs | 1 + compile_output.txt | 45 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 compile_output.txt diff --git a/WINGs/wings-rs/src/lib.rs b/WINGs/wings-rs/src/lib.rs index db2efa54..31ba6e97 100644 --- a/WINGs/wings-rs/src/lib.rs +++ b/WINGs/wings-rs/src/lib.rs @@ -1,3 +1,4 @@ +#[allow(improper_ctypes)] #[allow(non_camel_case_types)] #[allow(non_snake_case)] #[allow(non_upper_case_globals)] diff --git a/compile_output.txt b/compile_output.txt new file mode 100644 index 00000000..fc0b3b5b --- /dev/null +++ b/compile_output.txt @@ -0,0 +1,45 @@ +make all-recursive +make[1]: Entering directory '/home/stu/projects/vitrine/wmaker' +Making all in wrlib +make[2]: Entering directory '/home/stu/projects/vitrine/wmaker/wrlib' +make all-recursive +make[3]: Entering directory '/home/stu/projects/vitrine/wmaker/wrlib' +Making all in . +make[4]: Entering directory '/home/stu/projects/vitrine/wmaker/wrlib' +make[4]: Nothing to be done for 'all-am'. +make[4]: Leaving directory '/home/stu/projects/vitrine/wmaker/wrlib' +Making all in po +make[4]: Entering directory '/home/stu/projects/vitrine/wmaker/wrlib/po' +make[4]: Nothing to be done for 'all'. +make[4]: Leaving directory '/home/stu/projects/vitrine/wmaker/wrlib/po' +make[3]: Leaving directory '/home/stu/projects/vitrine/wmaker/wrlib' +make[2]: Leaving directory '/home/stu/projects/vitrine/wmaker/wrlib' +Making all in wutil-rs +make[2]: Entering directory '/home/stu/projects/vitrine/wmaker/wutil-rs' +cargo build +make[2]: Leaving directory '/home/stu/projects/vitrine/wmaker/wutil-rs' +Making all in WINGs +make[2]: Entering directory '/home/stu/projects/vitrine/wmaker/WINGs' +Making all in WINGs +make[3]: Entering directory '/home/stu/projects/vitrine/wmaker/WINGs/WINGs' +../../script/replace-ac-keywords.sh \ + --header "../../config.h" \ + --filter "USE_PANGO" \ + -o WINGsP.h WINGsP.h.in +make all-am +make[4]: Entering directory '/home/stu/projects/vitrine/wmaker/WINGs/WINGs' +make[4]: Nothing to be done for 'all-am'. +make[4]: Leaving directory '/home/stu/projects/vitrine/wmaker/WINGs/WINGs' +make[3]: Leaving directory '/home/stu/projects/vitrine/wmaker/WINGs/WINGs' +Making all in wings-rs +make[3]: Entering directory '/home/stu/projects/vitrine/wmaker/WINGs/wings-rs' +bindgen ../WINGs/WINGsP.h \ +--no-recursive-allowlist \ +--allowlist-type "^W_.+|^WM(View|Array|DragOperationType|Point|Data|OpenPanel|SavePanel|HashTable|DraggingInfo|SelectionProcs|Rect|EventProc|Widget|Size|Color|Pixmap|FilePanel)|R(Context|ContextAttributes|Image|RenderingMode|ScalingFilter|StdColormapMode|ImageFormat|Color)|_WINGsConfiguration" \ +--allowlist-type "^WM(FontPanel|Screen|Button)" \ +--allowlist-function "^WMCreateScreen|^WM(Get|Show)FontPanel|^WMCreateCommandButton|^WM(Initialize|Release)Application|^WMScreenMainLoop|^WMHandleEvent" \ +-o src/WINGsP.rs -- \ +-I/usr/include/pango-1.0 -I/usr/include/libmount -I/usr/include/blkid -I/usr/include/fribidi -I/usr/include/cairo -I/usr/include/pixman-1 -I/usr/include/harfbuzz -I/usr/include/freetype2 -I/usr/include/libpng16 -I/usr/include/glib-2.0 -I/usr/lib/aarch64-linux-gnu/glib-2.0/include -I/usr/include/sysprof-6 -pthread \ +-I../../wrlib \ +-I.. && ./patch_WINGsP.sh src/WINGsP.rs +cargo build -- 2.39.5 From 5883e0d8a350820683c132cbf1abdec6138f1147 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Sun, 5 Apr 2026 19:56:09 -0400 Subject: [PATCH 2/9] Eliminate accidentally commited temporary file. --- compile_output.txt | 45 --------------------------------------------- 1 file changed, 45 deletions(-) delete mode 100644 compile_output.txt diff --git a/compile_output.txt b/compile_output.txt deleted file mode 100644 index fc0b3b5b..00000000 --- a/compile_output.txt +++ /dev/null @@ -1,45 +0,0 @@ -make all-recursive -make[1]: Entering directory '/home/stu/projects/vitrine/wmaker' -Making all in wrlib -make[2]: Entering directory '/home/stu/projects/vitrine/wmaker/wrlib' -make all-recursive -make[3]: Entering directory '/home/stu/projects/vitrine/wmaker/wrlib' -Making all in . -make[4]: Entering directory '/home/stu/projects/vitrine/wmaker/wrlib' -make[4]: Nothing to be done for 'all-am'. -make[4]: Leaving directory '/home/stu/projects/vitrine/wmaker/wrlib' -Making all in po -make[4]: Entering directory '/home/stu/projects/vitrine/wmaker/wrlib/po' -make[4]: Nothing to be done for 'all'. -make[4]: Leaving directory '/home/stu/projects/vitrine/wmaker/wrlib/po' -make[3]: Leaving directory '/home/stu/projects/vitrine/wmaker/wrlib' -make[2]: Leaving directory '/home/stu/projects/vitrine/wmaker/wrlib' -Making all in wutil-rs -make[2]: Entering directory '/home/stu/projects/vitrine/wmaker/wutil-rs' -cargo build -make[2]: Leaving directory '/home/stu/projects/vitrine/wmaker/wutil-rs' -Making all in WINGs -make[2]: Entering directory '/home/stu/projects/vitrine/wmaker/WINGs' -Making all in WINGs -make[3]: Entering directory '/home/stu/projects/vitrine/wmaker/WINGs/WINGs' -../../script/replace-ac-keywords.sh \ - --header "../../config.h" \ - --filter "USE_PANGO" \ - -o WINGsP.h WINGsP.h.in -make all-am -make[4]: Entering directory '/home/stu/projects/vitrine/wmaker/WINGs/WINGs' -make[4]: Nothing to be done for 'all-am'. -make[4]: Leaving directory '/home/stu/projects/vitrine/wmaker/WINGs/WINGs' -make[3]: Leaving directory '/home/stu/projects/vitrine/wmaker/WINGs/WINGs' -Making all in wings-rs -make[3]: Entering directory '/home/stu/projects/vitrine/wmaker/WINGs/wings-rs' -bindgen ../WINGs/WINGsP.h \ ---no-recursive-allowlist \ ---allowlist-type "^W_.+|^WM(View|Array|DragOperationType|Point|Data|OpenPanel|SavePanel|HashTable|DraggingInfo|SelectionProcs|Rect|EventProc|Widget|Size|Color|Pixmap|FilePanel)|R(Context|ContextAttributes|Image|RenderingMode|ScalingFilter|StdColormapMode|ImageFormat|Color)|_WINGsConfiguration" \ ---allowlist-type "^WM(FontPanel|Screen|Button)" \ ---allowlist-function "^WMCreateScreen|^WM(Get|Show)FontPanel|^WMCreateCommandButton|^WM(Initialize|Release)Application|^WMScreenMainLoop|^WMHandleEvent" \ --o src/WINGsP.rs -- \ --I/usr/include/pango-1.0 -I/usr/include/libmount -I/usr/include/blkid -I/usr/include/fribidi -I/usr/include/cairo -I/usr/include/pixman-1 -I/usr/include/harfbuzz -I/usr/include/freetype2 -I/usr/include/libpng16 -I/usr/include/glib-2.0 -I/usr/lib/aarch64-linux-gnu/glib-2.0/include -I/usr/include/sysprof-6 -pthread \ --I../../wrlib \ --I.. && ./patch_WINGsP.sh src/WINGsP.rs -cargo build -- 2.39.5 From bf8a35e44a3a7e588f0d361d02187938512c27b2 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Mon, 15 Dec 2025 13:52:37 -0500 Subject: [PATCH 3/9] Name the anonymous enum for WMButton behaviors. This is needed so that bindgen cann refer to the enum properly when generating a corresponding Rust type. --- WINGs/WINGs/WINGs.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WINGs/WINGs/WINGs.h b/WINGs/WINGs/WINGs.h index b1ecad12..1d1a2927 100644 --- a/WINGs/WINGs/WINGs.h +++ b/WINGs/WINGs/WINGs.h @@ -132,7 +132,7 @@ typedef enum { } WMButtonType; /* button behaviour masks */ -enum { +typedef enum { WBBSpringLoadedMask = (1 << 0), WBBPushInMask = (1 << 1), WBBPushChangeMask = (1 << 2), @@ -140,7 +140,7 @@ enum { WBBStateLightMask = (1 << 5), WBBStateChangeMask = (1 << 6), WBBStatePushMask = (1 << 7) -}; +} WMButtonBehaviorMask; /* frame title positions */ -- 2.39.5 From 975089b963d9fd9dac625dbd66f827061f35fc3c Mon Sep 17 00:00:00 2001 From: Stu Black Date: Mon, 15 Dec 2025 13:52:37 -0500 Subject: [PATCH 4/9] Clean up bindgen invocation for WINGs somewhat. This is still a mess, but it's not all in one big line now. Going forward, commits that need new bindgen functions and types should put them on their own lines. That will make it easier to figure out which commit introduced the use of a given symbol. --- WINGs/wings-rs/Makefile.am | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/WINGs/wings-rs/Makefile.am b/WINGs/wings-rs/Makefile.am index aa9e1b23..156a967f 100644 --- a/WINGs/wings-rs/Makefile.am +++ b/WINGs/wings-rs/Makefile.am @@ -18,6 +18,36 @@ src/WINGsP.rs: ../WINGs/WINGsP.h ../../wrlib/wraster.h ../WINGs/WINGs.h ../WINGs --allowlist-type "^W_.+|^WM(View|Array|DragOperationType|Point|Data|OpenPanel|SavePanel|HashTable|DraggingInfo|SelectionProcs|Rect|EventProc|Widget|Size|Color|Pixmap|FilePanel)|R(Context|ContextAttributes|Image|RenderingMode|ScalingFilter|StdColormapMode|ImageFormat|Color)|_WINGsConfiguration" \ --allowlist-type "^WM(FontPanel|Screen|Button)" \ --allowlist-function "^WMCreateScreen|^WM(Get|Show)FontPanel|^WMCreateCommandButton|^WM(Initialize|Release)Application|^WMScreenMainLoop|^WMHandleEvent" \ + --allowlist-function "^WMWidgetScreen|^WM(Initialize|Release)Application|^WM(ScreenMainLoop|HandleEvent)|^WM(((Get|Set)TextFieldFont)|GetTextFieldText|SetTextFieldText|SelectTextFieldRange)" \ + --allowlist-type "WMList" \ + --allowlist-function "^WM(CreateList|SetListAction|GetListSelectedItem|InsertListItem|ClearList|FindRowOfListItemWithTitle|SelectListItem|SetListPosition|SortListItems)" \ + --allowlist-type "^WM(Window|Action)" \ + --allowlist-function "^WM(CreateWindow|SetWindowTitle|SetWindowMinSize|SetWindowCloseAction)" \ + --allowlist-type "^WMWidget" \ + --allowlist-function "^WM(Map|Destroy|Unmap|Resize|Realize|Move)Widget" \ + --allowlist-function "^WMWidget(Width|Height)" \ + --allowlist-function "^WM(SetWidgetBackgroundColor|SetWidgetBackgroundColor|MapSubwidgets)" \ + --allowlist-type "^WM(SplitView|SplitViewConstrainProc)" \ + --allowlist-function "^WM(SetViewNotifySizeChanges|CreateSplitView|SetSplitViewConstrainProc|GetSplitViewDividerThickness|AddSplitViewSubview)" \ + --allowlist-type "^WMNotification" \ + --allowlist-function "^WM(Add|Remove)NotificationObserver|^WMGetNotification(Object|Name)" \ + --allowlist-item "^WMNotificationObserverAction" \ + --allowlist-item "^WMViewSizeDidChangeNotification" \ + --allowlist-type "^WMFrame" \ + --allowlist-function "^WM(CreateFrame|SetFrameRelief)" \ + --allowlist-function "^WM(White|DarkGray|Release)Color" \ + --allowlist-type "^WMTextField" \ + --allowlist-function "^WM(CreateTextField)" \ + --allowlist-type "^WMLabel" \ + --allowlist-function "^WM(CreateLabel|SetLabelText|SetLabelFont|SetLabelTextColor|SetLabelRelief|SetLabelTextAlignment)" \ + --allowlist-type "^WM(Button|ButtonBehaviorMask)" \ + --allowlist-function "^WM(CreateCustomButton|SetButtonText|SetButtonAction|SetButtonText)" \ + --allowlist-function "wmkrange" \ + --allowlist-type "^WM(View|Array|DragOperationType|Point|Data|OpenPanel|SavePanel|HashTable|DraggingInfo|SelectionProcs|Rect|EventProc|Widget|Size|Color|Pixmap|FilePanel|Screen|Range|List|ListItem)" \ + --allowlist-type "^R(Context|ContextAttributes|Image|RenderingMode|ScalingFilter|StdColormapMode|ImageFormat|Color)" \ + --allowlist-type "_WINGsConfiguration" \ + --allowlist-item "^WMAlignment" \ + --allowlist-item "^WMReliefType" \ -o src/WINGsP.rs -- \ @PANGO_CFLAGS@ \ -I../../wrlib \ -- 2.39.5 From 6c72da04900dd015ac64680d1ab72108a61e1640 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Mon, 15 Dec 2025 13:52:37 -0500 Subject: [PATCH 5/9] Reimplment the WMCreateCommandButton macro in Rust. This is a macro in C, and it would be helpful to be able to do the same thing in Rust. It is simply ported over as a Rust function. --- WINGs/wings-rs/Makefile.am | 1 + WINGs/wings-rs/src/button.rs | 21 +++++++++++++++++++++ WINGs/wings-rs/src/lib.rs | 1 + 3 files changed, 23 insertions(+) create mode 100644 WINGs/wings-rs/src/button.rs diff --git a/WINGs/wings-rs/Makefile.am b/WINGs/wings-rs/Makefile.am index 156a967f..c08e7dea 100644 --- a/WINGs/wings-rs/Makefile.am +++ b/WINGs/wings-rs/Makefile.am @@ -2,6 +2,7 @@ AUTOMAKE_OPTIONS = RUST_SOURCES = \ src/WINGsP.rs \ + src/button.rs \ src/configuration.rs \ src/font.rs \ src/lib.rs \ diff --git a/WINGs/wings-rs/src/button.rs b/WINGs/wings-rs/src/button.rs new file mode 100644 index 00000000..b863381a --- /dev/null +++ b/WINGs/wings-rs/src/button.rs @@ -0,0 +1,21 @@ +pub mod ffi { + use crate::WINGsP::*; + + /// Creates a button like an "Ok" button to dismiss a dialog. + /// + /// ## Rust rewrite notes + /// + /// This was originally a macro. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreateCommandButton(parent: *mut WMWidget) -> *mut WMButton { + unsafe { + WMCreateCustomButton( + parent, + (WMButtonBehaviorMask_WBBSpringLoadedMask + | WMButtonBehaviorMask_WBBPushInMask + | WMButtonBehaviorMask_WBBPushLightMask + | WMButtonBehaviorMask_WBBPushChangeMask) as i32, + ) + } + } +} diff --git a/WINGs/wings-rs/src/lib.rs b/WINGs/wings-rs/src/lib.rs index 31ba6e97..284fa4fa 100644 --- a/WINGs/wings-rs/src/lib.rs +++ b/WINGs/wings-rs/src/lib.rs @@ -3,6 +3,7 @@ #[allow(non_snake_case)] #[allow(non_upper_case_globals)] pub mod WINGsP; +pub mod button; pub mod configuration; pub mod font; pub(crate) mod pango_extras; -- 2.39.5 From d4168b821ef8656abab50deb028122b23b85e312 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Mon, 15 Dec 2025 13:52:37 -0500 Subject: [PATCH 6/9] Reimplement the WMAddListItem macro in Rust. This was originally a macro in C. We reimplement it as a Rust function. --- WINGs/wings-rs/Makefile.am | 1 + WINGs/wings-rs/src/lib.rs | 1 + WINGs/wings-rs/src/list.rs | 14 ++++++++++++++ 3 files changed, 16 insertions(+) create mode 100644 WINGs/wings-rs/src/list.rs diff --git a/WINGs/wings-rs/Makefile.am b/WINGs/wings-rs/Makefile.am index c08e7dea..1b99a146 100644 --- a/WINGs/wings-rs/Makefile.am +++ b/WINGs/wings-rs/Makefile.am @@ -6,6 +6,7 @@ RUST_SOURCES = \ src/configuration.rs \ src/font.rs \ src/lib.rs \ + src/list.rs \ src/pango_extras.rs \ src/screen.rs diff --git a/WINGs/wings-rs/src/lib.rs b/WINGs/wings-rs/src/lib.rs index 284fa4fa..9885e498 100644 --- a/WINGs/wings-rs/src/lib.rs +++ b/WINGs/wings-rs/src/lib.rs @@ -6,5 +6,6 @@ pub mod WINGsP; pub mod button; pub mod configuration; pub mod font; +pub mod list; pub(crate) mod pango_extras; pub mod screen; diff --git a/WINGs/wings-rs/src/list.rs b/WINGs/wings-rs/src/list.rs new file mode 100644 index 00000000..03cdacd1 --- /dev/null +++ b/WINGs/wings-rs/src/list.rs @@ -0,0 +1,14 @@ +pub mod ffi { + use crate::WINGsP::*; + + use std::ffi::c_char; + + /// Appends an item with the label `text` to `list`. + /// + /// ## Rust rewrite notes + /// + /// This was originally a macro. + pub unsafe fn WMAddListItem(list: *mut WMList, text: *const c_char) -> *mut WMListItem { + unsafe { WMInsertListItem(list, -1, text) } + } +} -- 2.39.5 From c79b5853a4c52e8e5de19e6f53a82d3fc3ec5393 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Mon, 15 Dec 2025 13:52:37 -0500 Subject: [PATCH 7/9] Start porting the base WINGs widget type to Rust. Porting WINGs widgets to Rust will require using some WINGs abstractions. One of them is the base widget type, which is implemented by giving all widgets a common layout (with the first two fields always being the same basic types). --- WINGs/wings-rs/Makefile.am | 3 +- WINGs/wings-rs/src/lib.rs | 1 + WINGs/wings-rs/src/widget.rs | 57 ++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 WINGs/wings-rs/src/widget.rs diff --git a/WINGs/wings-rs/Makefile.am b/WINGs/wings-rs/Makefile.am index 1b99a146..3032b9b5 100644 --- a/WINGs/wings-rs/Makefile.am +++ b/WINGs/wings-rs/Makefile.am @@ -8,7 +8,8 @@ RUST_SOURCES = \ src/lib.rs \ src/list.rs \ src/pango_extras.rs \ - src/screen.rs + src/screen.rs \ + src/widget.rs RUST_EXTRA = \ Cargo.lock \ diff --git a/WINGs/wings-rs/src/lib.rs b/WINGs/wings-rs/src/lib.rs index 9885e498..1609fe1d 100644 --- a/WINGs/wings-rs/src/lib.rs +++ b/WINGs/wings-rs/src/lib.rs @@ -9,3 +9,4 @@ pub mod font; pub mod list; pub(crate) mod pango_extras; pub mod screen; +pub mod widget; diff --git a/WINGs/wings-rs/src/widget.rs b/WINGs/wings-rs/src/widget.rs new file mode 100644 index 00000000..27ec7bff --- /dev/null +++ b/WINGs/wings-rs/src/widget.rs @@ -0,0 +1,57 @@ +//! Common WINGs widget fields. +//! +//! WINGs widgets all have a [`WINGsP::WMView`] object that tracks common attributes +//! like size and location and a [`WINGsP::W_Class`] field that identifies the widget +//! type (button, list, etc.). This module provides access to these fields. +//! +//! ## Rust rewrite notes +//! +//! The original WINGs implementation uses casting and a common layout for +//! "primitive" widget structs, such that the first two fields are always a +//! `W_Class` and a `*mut WMView`. We rely on this convention when implementing +//! [`Widget`] for widgets that are defined in C. +use crate::WINGsP; +use std::{ + ptr::NonNull, +}; + +/// Base WINGs widget type. +/// +/// All primitive widgets (like buttons and lists) should implement this. +pub trait Widget { + fn class(&self) -> WINGsP::W_Class; + fn view(&self) -> NonNull; +} + +/// Common widget header fields. It should be safe to cast opaque WINGs widget +/// structs defined in C to this type to access these fields. Before writing +/// code that casts anything to this, double-check the widget definition! +#[repr(C)] +struct WidgetLike { + class: WINGsP::W_Class, + view: *mut WINGsP::WMView, +} + +/// Casts `$widget` to [`WidgetLike`]. Provided for convenience. +/// +/// ## Safety +/// +/// The caller of this macro is responsible for ensuring that the layout of `$widget` is compatible +/// with `WidgetLike`. +macro_rules! impl_widget { + ($widget:ty) => { + impl Widget for $widget { + fn class(&self) -> crate::WINGsP::W_Class { + unsafe { ::std::mem::transmute::<&$widget, &WidgetLike>(self).class } + } + + fn view(&self) -> ::std::ptr::NonNull { + let widget = unsafe { ::std::mem::transmute::<&$widget, &WidgetLike>(self) }; + NonNull::new(widget.view).expect("WMView field of widget struct cannot be null") + } + } + }; +} + +impl_widget!(WINGsP::WMWindow); +impl_widget!(WINGsP::WMFrame); -- 2.39.5 From 2a83b08f0cad470d09e92bd6f216c8fc0a77f450 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Sun, 5 Apr 2026 20:38:00 -0400 Subject: [PATCH 8/9] Add allow(non_snake_case) for `WMList` and `WMButton` FFI functions. --- WINGs/wings-rs/src/button.rs | 1 + WINGs/wings-rs/src/list.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/WINGs/wings-rs/src/button.rs b/WINGs/wings-rs/src/button.rs index b863381a..0525500a 100644 --- a/WINGs/wings-rs/src/button.rs +++ b/WINGs/wings-rs/src/button.rs @@ -1,3 +1,4 @@ +#[allow(non_snake_case)] pub mod ffi { use crate::WINGsP::*; diff --git a/WINGs/wings-rs/src/list.rs b/WINGs/wings-rs/src/list.rs index 03cdacd1..dcfbaf34 100644 --- a/WINGs/wings-rs/src/list.rs +++ b/WINGs/wings-rs/src/list.rs @@ -1,3 +1,4 @@ +#[allow(non_snake_case)] pub mod ffi { use crate::WINGsP::*; -- 2.39.5 From 80635c128ab6c3d26beda9e43e49f3814017a4d3 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Mon, 15 Dec 2025 13:52:37 -0500 Subject: [PATCH 9/9] Rewrite WMFontPanel in Rust. --- WINGs/Makefile.am | 1 - WINGs/Tests/Makefile.am | 9 +- WINGs/WINGs/WINGsP.h.in | 2 - WINGs/wfontpanel.c | 825 ------------- WINGs/wings-rs-tests/examples/font_panel.rs | 6 +- .../wings-rs-tests/tests/font_panel_tests.rs | 11 +- WINGs/wings-rs/Makefile.am | 4 + WINGs/wings-rs/src/font_panel.rs | 1019 +++++++++++++++++ WINGs/wings-rs/src/lib.rs | 1 + 9 files changed, 1038 insertions(+), 840 deletions(-) delete mode 100644 WINGs/wfontpanel.c create mode 100644 WINGs/wings-rs/src/font_panel.rs diff --git a/WINGs/Makefile.am b/WINGs/Makefile.am index 942c619c..5f8fa51a 100644 --- a/WINGs/Makefile.am +++ b/WINGs/Makefile.am @@ -41,7 +41,6 @@ libWINGs_la_SOURCES = \ wevent.c \ wfilepanel.c \ wframe.c \ - wfontpanel.c \ widgets.c \ winputmethod.c \ wlabel.c \ diff --git a/WINGs/Tests/Makefile.am b/WINGs/Tests/Makefile.am index 98f0e458..585c0e5b 100644 --- a/WINGs/Tests/Makefile.am +++ b/WINGs/Tests/Makefile.am @@ -4,14 +4,17 @@ AUTOMAKE_OPTIONS = noinst_PROGRAMS = wtest wmquery wmfile testmywidget -LDADD= $(top_builddir)/WINGs/libWINGs.la $(top_builddir)/wrlib/libwraster.la \ +LDADD= $(top_builddir)/WINGs/libWINGs.la \ + $(top_builddir)/wrlib/libwraster.la \ $(top_builddir)/WINGs/libWUtil.la \ - @XFT_LIBS@ @INTLIBS@ @XLIBS@ + $(top_builddir)/WINGs/wings-rs/target/debug/libwings_rs.a \ + @XFT_LIBS@ @INTLIBS@ @XLIBS@ @FCLIBS@ @PANGO_LIBS@ testmywidget_SOURCES = testmywidget.c mywidget.c mywidget.h -wtest_DEPENDENCIES = $(top_builddir)/WINGs/libWINGs.la +wtest_DEPENDENCIES = $(top_builddir)/WINGs/libWINGs.la \ + $(top_builddir)/WINGs/wings-rs/target/debug/libwings_rs.a EXTRA_DIST = logo.xpm upbtn.xpm wm.html wm.png diff --git a/WINGs/WINGs/WINGsP.h.in b/WINGs/WINGs/WINGsP.h.in index 56829a92..b7ed025a 100644 --- a/WINGs/WINGs/WINGsP.h.in +++ b/WINGs/WINGs/WINGsP.h.in @@ -122,8 +122,6 @@ typedef struct W_Screen { WMOpenPanel *sharedOpenPanel; WMSavePanel *sharedSavePanel; - struct W_FontPanel *sharedFontPanel; - struct W_ColorPanel *sharedColorPanel; Pixmap stipple; diff --git a/WINGs/wfontpanel.c b/WINGs/wfontpanel.c deleted file mode 100644 index 9b5b001d..00000000 --- a/WINGs/wfontpanel.c +++ /dev/null @@ -1,825 +0,0 @@ - -#include "WINGsP.h" -#include "WUtil.h" -#include "wconfig.h" - -#include -#include -#include -#include - -#include -#include - -/* XXX TODO */ -char *WMFontPanelFontChangedNotification = "WMFontPanelFontChangedNotification"; - -typedef struct W_FontPanel { - WMWindow *win; - - WMFrame *upperF; - WMTextField *sampleT; - - WMSplitView *split; - - WMFrame *lowerF; - WMLabel *famL; - WMList *famLs; - WMLabel *typL; - WMList *typLs; - WMLabel *sizL; - WMTextField *sizT; - WMList *sizLs; - - WMAction2 *action; - void *data; - - WMButton *revertB; - WMButton *setB; - - WMPropList *fdb; -} FontPanel; - -#define MIN_UPPER_HEIGHT 20 -#define MIN_LOWER_HEIGHT 140 - -#define BUTTON_SPACE_HEIGHT 40 - -#define MIN_WIDTH 250 -#define MIN_HEIGHT (MIN_UPPER_HEIGHT+MIN_LOWER_HEIGHT+BUTTON_SPACE_HEIGHT) - -#define DEF_UPPER_HEIGHT 60 -#define DEF_LOWER_HEIGHT 310 - -#define DEF_WIDTH 320 -#define DEF_HEIGHT (DEF_UPPER_HEIGHT+DEF_LOWER_HEIGHT) - -static const int scalableFontSizes[] = { - 8, - 10, - 11, - 12, - 14, - 16, - 18, - 20, - 24, - 36, - 48, - 64 -}; - -static void setFontPanelFontName(FontPanel * panel, const char *family, const char *style, double size); - -static int isXLFD(const char *font, int *length_ret); - -static void arrangeLowerFrame(FontPanel * panel); - -static void familyClick(WMWidget *, void *); -static void typefaceClick(WMWidget *, void *); -static void sizeClick(WMWidget *, void *); - -static void listFamilies(WMScreen * scr, WMFontPanel * panel); - -static void splitViewConstrainCallback(WMSplitView * sPtr, int indView, int *min, int *max) -{ - /* Parameter not used, but tell the compiler that it is ok */ - (void) sPtr; - (void) max; - - if (indView == 0) - *min = MIN_UPPER_HEIGHT; - else - *min = MIN_LOWER_HEIGHT; -} - -static void notificationObserver(void *self, WMNotification * notif) -{ - WMFontPanel *panel = (WMFontPanel *) self; - void *object = WMGetNotificationObject(notif); - - if (WMGetNotificationName(notif) == WMViewSizeDidChangeNotification) { - if (object == WMWidgetView(panel->win)) { - int h = WMWidgetHeight(panel->win); - int w = WMWidgetWidth(panel->win); - - WMResizeWidget(panel->split, w, h - BUTTON_SPACE_HEIGHT); - - WMMoveWidget(panel->setB, w - 80, h - (BUTTON_SPACE_HEIGHT - 5)); - - WMMoveWidget(panel->revertB, w - 240, h - (BUTTON_SPACE_HEIGHT - 5)); - - } else if (object == WMWidgetView(panel->upperF)) { - - if (WMWidgetHeight(panel->upperF) < MIN_UPPER_HEIGHT) { - WMResizeWidget(panel->upperF, WMWidgetWidth(panel->upperF), MIN_UPPER_HEIGHT); - } else { - WMResizeWidget(panel->sampleT, WMWidgetWidth(panel->upperF) - 20, - WMWidgetHeight(panel->upperF) - 10); - } - - } else if (object == WMWidgetView(panel->lowerF)) { - - if (WMWidgetHeight(panel->lowerF) < MIN_LOWER_HEIGHT) { - WMResizeWidget(panel->upperF, WMWidgetWidth(panel->upperF), MIN_UPPER_HEIGHT); - - WMMoveWidget(panel->lowerF, 0, WMWidgetHeight(panel->upperF) - + WMGetSplitViewDividerThickness(panel->split)); - - WMResizeWidget(panel->lowerF, WMWidgetWidth(panel->lowerF), - WMWidgetWidth(panel->split) - MIN_UPPER_HEIGHT - - WMGetSplitViewDividerThickness(panel->split)); - } else { - arrangeLowerFrame(panel); - } - } - } -} - -static void closeWindow(WMWidget * w, void *data) -{ - FontPanel *panel = (FontPanel *) data; - - /* Parameter not used, but tell the compiler that it is ok */ - (void) w; - - WMHideFontPanel(panel); -} - -static void setClickedAction(WMWidget * w, void *data) -{ - FontPanel *panel = (FontPanel *) data; - - /* Parameter not used, but tell the compiler that it is ok */ - (void) w; - - if (panel->action) - (*panel->action) (panel, panel->data); -} - -static void revertClickedAction(WMWidget * w, void *data) -{ - /* Parameter not used, but tell the compiler that it is ok */ - (void) w; - (void) data; - - /*FontPanel *panel = (FontPanel*)data; */ - /* XXX TODO */ -} - -WMFontPanel *WMGetFontPanel(WMScreen * scr) -{ - FontPanel *panel; - WMColor *dark, *white; - WMFont *font; - int divThickness; - - if (scr->sharedFontPanel) - return scr->sharedFontPanel; - - panel = wmalloc(sizeof(FontPanel)); - - panel->win = WMCreateWindow(scr, "fontPanel"); - /* WMSetWidgetBackgroundColor(panel->win, WMWhiteColor(scr)); */ - WMSetWindowTitle(panel->win, _("Font Panel")); - WMResizeWidget(panel->win, DEF_WIDTH, DEF_HEIGHT); - WMSetWindowMinSize(panel->win, MIN_WIDTH, MIN_HEIGHT); - WMSetViewNotifySizeChanges(WMWidgetView(panel->win), True); - - WMSetWindowCloseAction(panel->win, closeWindow, panel); - - panel->split = WMCreateSplitView(panel->win); - WMResizeWidget(panel->split, DEF_WIDTH, DEF_HEIGHT - BUTTON_SPACE_HEIGHT); - WMSetSplitViewConstrainProc(panel->split, splitViewConstrainCallback); - - divThickness = WMGetSplitViewDividerThickness(panel->split); - - panel->upperF = WMCreateFrame(panel->win); - WMSetFrameRelief(panel->upperF, WRFlat); - WMSetViewNotifySizeChanges(WMWidgetView(panel->upperF), True); - panel->lowerF = WMCreateFrame(panel->win); - /* WMSetWidgetBackgroundColor(panel->lowerF, WMBlackColor(scr)); */ - WMSetFrameRelief(panel->lowerF, WRFlat); - WMSetViewNotifySizeChanges(WMWidgetView(panel->lowerF), True); - - WMAddSplitViewSubview(panel->split, W_VIEW(panel->upperF)); - WMAddSplitViewSubview(panel->split, W_VIEW(panel->lowerF)); - - WMResizeWidget(panel->upperF, DEF_WIDTH, DEF_UPPER_HEIGHT); - - WMResizeWidget(panel->lowerF, DEF_WIDTH, DEF_LOWER_HEIGHT); - - WMMoveWidget(panel->lowerF, 0, 60 + divThickness); - - white = WMWhiteColor(scr); - dark = WMDarkGrayColor(scr); - - panel->sampleT = WMCreateTextField(panel->upperF); - WMResizeWidget(panel->sampleT, DEF_WIDTH - 20, 50); - WMMoveWidget(panel->sampleT, 10, 10); - WMSetTextFieldText(panel->sampleT, _("The quick brown fox jumps over the lazy dog")); - - font = WMBoldSystemFontOfSize(scr, 12); - - panel->famL = WMCreateLabel(panel->lowerF); - WMSetWidgetBackgroundColor(panel->famL, dark); - WMSetLabelText(panel->famL, _("Family")); - WMSetLabelFont(panel->famL, font); - WMSetLabelTextColor(panel->famL, white); - WMSetLabelRelief(panel->famL, WRSunken); - WMSetLabelTextAlignment(panel->famL, WACenter); - - panel->famLs = WMCreateList(panel->lowerF); - WMSetListAction(panel->famLs, familyClick, panel); - - panel->typL = WMCreateLabel(panel->lowerF); - WMSetWidgetBackgroundColor(panel->typL, dark); - WMSetLabelText(panel->typL, _("Typeface")); - WMSetLabelFont(panel->typL, font); - WMSetLabelTextColor(panel->typL, white); - WMSetLabelRelief(panel->typL, WRSunken); - WMSetLabelTextAlignment(panel->typL, WACenter); - - panel->typLs = WMCreateList(panel->lowerF); - WMSetListAction(panel->typLs, typefaceClick, panel); - - panel->sizL = WMCreateLabel(panel->lowerF); - WMSetWidgetBackgroundColor(panel->sizL, dark); - WMSetLabelText(panel->sizL, _("Size")); - WMSetLabelFont(panel->sizL, font); - WMSetLabelTextColor(panel->sizL, white); - WMSetLabelRelief(panel->sizL, WRSunken); - WMSetLabelTextAlignment(panel->sizL, WACenter); - - panel->sizT = WMCreateTextField(panel->lowerF); - /* WMSetTextFieldAlignment(panel->sizT, WARight); */ - - panel->sizLs = WMCreateList(panel->lowerF); - WMSetListAction(panel->sizLs, sizeClick, panel); - - WMReleaseFont(font); - WMReleaseColor(white); - WMReleaseColor(dark); - - panel->setB = WMCreateCommandButton(panel->win); - WMResizeWidget(panel->setB, 70, 24); - WMMoveWidget(panel->setB, 240, DEF_HEIGHT - (BUTTON_SPACE_HEIGHT - 5)); - WMSetButtonText(panel->setB, _("Set")); - WMSetButtonAction(panel->setB, setClickedAction, panel); - - panel->revertB = WMCreateCommandButton(panel->win); - WMResizeWidget(panel->revertB, 70, 24); - WMMoveWidget(panel->revertB, 80, DEF_HEIGHT - (BUTTON_SPACE_HEIGHT - 5)); - WMSetButtonText(panel->revertB, _("Revert")); - WMSetButtonAction(panel->revertB, revertClickedAction, panel); - - WMRealizeWidget(panel->win); - - WMMapSubwidgets(panel->upperF); - WMMapSubwidgets(panel->lowerF); - WMMapSubwidgets(panel->split); - WMMapSubwidgets(panel->win); - - WMUnmapWidget(panel->revertB); - - arrangeLowerFrame(panel); - - scr->sharedFontPanel = panel; - - /* register notification observers */ - WMAddNotificationObserver(notificationObserver, panel, - WMViewSizeDidChangeNotification, WMWidgetView(panel->win)); - WMAddNotificationObserver(notificationObserver, panel, - WMViewSizeDidChangeNotification, WMWidgetView(panel->upperF)); - WMAddNotificationObserver(notificationObserver, panel, - WMViewSizeDidChangeNotification, WMWidgetView(panel->lowerF)); - - listFamilies(scr, panel); - - return panel; -} - -void WMFreeFontPanel(WMFontPanel * panel) -{ - if (panel == WMWidgetScreen(panel->win)->sharedFontPanel) { - WMWidgetScreen(panel->win)->sharedFontPanel = NULL; - } - WMRemoveNotificationObserver(panel); - WMUnmapWidget(panel->win); - WMDestroyWidget(panel->win); - wfree(panel); -} - -void WMShowFontPanel(WMFontPanel * panel) -{ - WMMapWidget(panel->win); -} - -void WMHideFontPanel(WMFontPanel * panel) -{ - WMUnmapWidget(panel->win); -} - -WMFont *WMGetFontPanelFont(WMFontPanel * panel) -{ - return WMGetTextFieldFont(panel->sampleT); -} - -void WMSetFontPanelFont(WMFontPanel * panel, const char *fontName) -{ - int fname_len; - FcPattern *pattern; - FcChar8 *family, *style; - double size; - - if (!isXLFD(fontName, &fname_len)) { - /* maybe its proper fontconfig and we can parse it */ - pattern = FcNameParse((const FcChar8 *) fontName); - } else { - /* maybe its proper xlfd and we can convert it to an FcPattern */ - pattern = XftXlfdParse(fontName, False, False); - /*//FcPatternPrint(pattern); */ - } - - if (!pattern) - return; - - if (FcPatternGetString(pattern, FC_FAMILY, 0, &family) == FcResultMatch) - if (FcPatternGetString(pattern, FC_STYLE, 0, &style) == FcResultMatch) - if (FcPatternGetDouble(pattern, "pixelsize", 0, &size) == FcResultMatch) - setFontPanelFontName(panel, (char *)family, (char *)style, size); - - FcPatternDestroy(pattern); -} - -void WMSetFontPanelAction(WMFontPanel * panel, WMAction2 * action, void *data) -{ - panel->action = action; - panel->data = data; -} - -static void arrangeLowerFrame(FontPanel * panel) -{ - int width = WMWidgetWidth(panel->lowerF) - 55 - 30; - int height = WMWidgetHeight(panel->split) - WMWidgetHeight(panel->upperF); - int fw, tw, sw; - -#define LABEL_HEIGHT 20 - - height -= WMGetSplitViewDividerThickness(panel->split); - - height -= LABEL_HEIGHT + 8; - - fw = (125 * width) / 235; - tw = (110 * width) / 235; - sw = 55; - - WMMoveWidget(panel->famL, 10, 0); - WMResizeWidget(panel->famL, fw, LABEL_HEIGHT); - - WMMoveWidget(panel->famLs, 10, 23); - WMResizeWidget(panel->famLs, fw, height); - - WMMoveWidget(panel->typL, 10 + fw + 3, 0); - WMResizeWidget(panel->typL, tw, LABEL_HEIGHT); - - WMMoveWidget(panel->typLs, 10 + fw + 3, 23); - WMResizeWidget(panel->typLs, tw, height); - - WMMoveWidget(panel->sizL, 10 + fw + 3 + tw + 3, 0); - WMResizeWidget(panel->sizL, sw + 4, LABEL_HEIGHT); - - WMMoveWidget(panel->sizT, 10 + fw + 3 + tw + 3, 23); - WMResizeWidget(panel->sizT, sw + 4, 20); - - WMMoveWidget(panel->sizLs, 10 + fw + 3 + tw + 3, 46); - WMResizeWidget(panel->sizLs, sw + 4, height - 23); -} - -#define NUM_FIELDS 14 - -static int isXLFD(const char *font, int *length_ret) -{ - int c = 0; - - *length_ret = 0; - while (*font) { - (*length_ret)++; - if (*font++ == '-') - c++; - } - - return c == NUM_FIELDS; -} - -typedef struct { - char *typeface; - WMArray *sizes; -} Typeface; - -typedef struct { - char *name; /* gotta love simplicity */ - WMArray *typefaces; -} Family; - -static int compare_int(const void *a, const void *b) -{ - int i1 = *(int *)a; - int i2 = *(int *)b; - - if (i1 < i2) - return -1; - else if (i1 > i2) - return 1; - else - return 0; -} - -static void addSizeToTypeface(Typeface * face, int size) -{ - if (size == 0) { - int j; - - for (j = 0; j < wlengthof(scalableFontSizes); j++) { - size = scalableFontSizes[j]; - - if (!WMCountInArray(face->sizes, (void *)(uintptr_t) size)) { - WMAddToArray(face->sizes, (void *)(uintptr_t) size); - } - } - WMSortArray(face->sizes, compare_int); - } else { - if (!WMCountInArray(face->sizes, (void *)(uintptr_t) size)) { - WMAddToArray(face->sizes, (void *)(uintptr_t) size); - WMSortArray(face->sizes, compare_int); - } - } -} - -static void addTypefaceToXftFamily(Family * fam, const char *style) -{ - Typeface *face; - WMArrayIterator i; - - if (fam->typefaces) { - WM_ITERATE_ARRAY(fam->typefaces, face, i) { - if (strcmp(face->typeface, style) != 0) - continue; /* go to next interation */ - addSizeToTypeface(face, 0); - return; - } - } else { - fam->typefaces = WMCreateArray(4); - } - - face = wmalloc(sizeof(Typeface)); - - face->typeface = wstrdup(style); - face->sizes = WMCreateArray(4); - addSizeToTypeface(face, 0); - - WMAddToArray(fam->typefaces, face); -} - -/* - * families (same family name) (Hashtable of family -> array) - * registries (same family but different registries) - * - */ -static void addFontToXftFamily(WMHashTable * families, const char *name, const char *style) -{ - WMArrayIterator i; - WMArray *array; - Family *fam; - - array = WMHashGet(families, name); - if (array) { - WM_ITERATE_ARRAY(array, fam, i) { - if (strcmp(fam->name, name) == 0) - addTypefaceToXftFamily(fam, style); - return; - } - } - - array = WMCreateArray(8); - - fam = wmalloc(sizeof(Family)); - - fam->name = wstrdup(name); - - addTypefaceToXftFamily(fam, style); - - WMAddToArray(array, fam); - - WMHashInsert(families, fam->name, array); -} - -static void listFamilies(WMScreen * scr, WMFontPanel * panel) -{ - FcObjectSet *os = 0; - FcFontSet *fs; - FcPattern *pat; - WMHashTable *families; - WMHashEnumerator *enumer; - WMArray *array; - int i; - - pat = FcPatternCreate(); - os = FcObjectSetBuild(FC_FAMILY, FC_STYLE, NULL); - fs = FcFontList(0, pat, os); - if (!fs) { - WMRunAlertPanel(scr, panel->win, _("Error"), - _("Could not init font config library\n"), _("OK"), NULL, NULL); - return; - } - if (pat) - FcPatternDestroy(pat); - - families = WMCreateStringHashTable(); - - if (fs) { - for (i = 0; i < fs->nfont; i++) { - FcChar8 *family; - FcChar8 *style; - - if (FcPatternGetString(fs->fonts[i], FC_FAMILY, 0, &family) == FcResultMatch) - if (FcPatternGetString(fs->fonts[i], FC_STYLE, 0, &style) == FcResultMatch) - addFontToXftFamily(families, (char *)family, (char *)style); - } - FcFontSetDestroy(fs); - } - - enumer = WMEnumerateHashTable(families); - - while ((array = WMNextHashEnumeratorItem(enumer))) { - WMArrayIterator i; - Family *fam; - char buffer[256]; - WMListItem *item; - - WM_ITERATE_ARRAY(array, fam, i) { - strlcpy(buffer, fam->name, sizeof(buffer)); - item = WMAddListItem(panel->famLs, buffer); - - item->clientData = fam; - } - - WMFreeArray(array); - } - - WMSortListItems(panel->famLs); - - WMFreeHashTable(families); -} - -static void getSelectedFont(FontPanel * panel, char buffer[], int bufsize) -{ - WMListItem *item; - Family *family; - Typeface *face; - char *size; - - item = WMGetListSelectedItem(panel->famLs); - if (!item) - return; - family = (Family *) item->clientData; - - item = WMGetListSelectedItem(panel->typLs); - if (!item) - return; - face = (Typeface *) item->clientData; - - size = WMGetTextFieldText(panel->sizT); - - snprintf(buffer, bufsize, "%s:style=%s:pixelsize=%s", family->name, face->typeface, size); - - wfree(size); -} - -static void preview(FontPanel * panel) -{ - char buffer[512]; - WMFont *font; - - getSelectedFont(panel, buffer, sizeof(buffer)); - font = WMCreateFont(WMWidgetScreen(panel->win), buffer); - if (font) { - WMSetTextFieldFont(panel->sampleT, font); - WMReleaseFont(font); - } -} - -static void familyClick(WMWidget * w, void *data) -{ - WMList *lPtr = (WMList *) w; - WMListItem *item; - Family *family; - Typeface *face; - FontPanel *panel = (FontPanel *) data; - WMArrayIterator i; - /* current typeface and size */ - char *oface = NULL; - char *osize = NULL; - int facei = -1; - int sizei = -1; - - /* must try to keep the same typeface and size for the new family */ - item = WMGetListSelectedItem(panel->typLs); - if (item) - oface = wstrdup(item->text); - - osize = WMGetTextFieldText(panel->sizT); - - item = WMGetListSelectedItem(lPtr); - family = (Family *) item->clientData; - - WMClearList(panel->typLs); - - WM_ITERATE_ARRAY(family->typefaces, face, i) { - char buffer[256]; - int top = 0; - WMListItem *fitem; - - strlcpy(buffer, face->typeface, sizeof(buffer)); - if (strcasecmp(face->typeface, "Roman") == 0) - top = 1; - if (strcasecmp(face->typeface, "Regular") == 0) - top = 1; - if (top) - fitem = WMInsertListItem(panel->typLs, 0, buffer); - else - fitem = WMAddListItem(panel->typLs, buffer); - fitem->clientData = face; - } - - if (oface) { - facei = WMFindRowOfListItemWithTitle(panel->typLs, oface); - wfree(oface); - } - if (facei < 0) { - facei = 0; - } - WMSelectListItem(panel->typLs, facei); - typefaceClick(panel->typLs, panel); - - if (osize) { - sizei = WMFindRowOfListItemWithTitle(panel->sizLs, osize); - } - if (sizei >= 0) { - WMSelectListItem(panel->sizLs, sizei); - sizeClick(panel->sizLs, panel); - } - - if (osize) - wfree(osize); - - preview(panel); -} - -static void typefaceClick(WMWidget * w, void *data) -{ - FontPanel *panel = (FontPanel *) data; - WMListItem *item; - Typeface *face; - WMArrayIterator i; - char buffer[32]; - - char *osize = NULL; - int sizei = -1; - void *size; - - /* Parameter not used, but tell the compiler that it is ok */ - (void) w; - - osize = WMGetTextFieldText(panel->sizT); - - item = WMGetListSelectedItem(panel->typLs); - face = (Typeface *) item->clientData; - - WMClearList(panel->sizLs); - - WM_ITERATE_ARRAY(face->sizes, size, i) { - if (size != NULL) { - int size_int = (intptr_t) size; - - sprintf(buffer, "%i", size_int); - - WMAddListItem(panel->sizLs, buffer); - } - } - - if (osize) { - sizei = WMFindRowOfListItemWithTitle(panel->sizLs, osize); - } - if (sizei < 0) { - sizei = WMFindRowOfListItemWithTitle(panel->sizLs, "12"); - } - if (sizei < 0) { - sizei = 0; - } - WMSelectListItem(panel->sizLs, sizei); - WMSetListPosition(panel->sizLs, sizei); - sizeClick(panel->sizLs, panel); - - if (osize) - wfree(osize); - - preview(panel); -} - -static void sizeClick(WMWidget * w, void *data) -{ - FontPanel *panel = (FontPanel *) data; - WMListItem *item; - - /* Parameter not used, but tell the compiler that it is ok */ - (void) w; - - item = WMGetListSelectedItem(panel->sizLs); - WMSetTextFieldText(panel->sizT, item->text); - - WMSelectTextFieldRange(panel->sizT, wmkrange(0, strlen(item->text))); - - preview(panel); -} - -static void setFontPanelFontName(FontPanel * panel, const char *family, const char *style, double size) -{ - int famrow; - int stlrow; - int sz; - char asize[64]; - void *vsize; - WMListItem *item; - Family *fam; - Typeface *face; - WMArrayIterator i; - - famrow = WMFindRowOfListItemWithTitle(panel->famLs, family); - if (famrow < 0) { - famrow = 0; - return; - } - WMSelectListItem(panel->famLs, famrow); - WMSetListPosition(panel->famLs, famrow); - - WMClearList(panel->typLs); - - item = WMGetListSelectedItem(panel->famLs); - - fam = (Family *) item->clientData; - WM_ITERATE_ARRAY(fam->typefaces, face, i) { - char buffer[256]; - int top = 0; - WMListItem *fitem; - - strlcpy(buffer, face->typeface, sizeof(buffer)); - if (strcasecmp(face->typeface, "Roman") == 0) - top = 1; - if (top) - fitem = WMInsertListItem(panel->typLs, 0, buffer); - else - fitem = WMAddListItem(panel->typLs, buffer); - fitem->clientData = face; - } - - stlrow = WMFindRowOfListItemWithTitle(panel->typLs, style); - - if (stlrow < 0) { - stlrow = 0; - return; - } - - WMSelectListItem(panel->typLs, stlrow); - - item = WMGetListSelectedItem(panel->typLs); - - face = (Typeface *) item->clientData; - - WMClearList(panel->sizLs); - - WM_ITERATE_ARRAY(face->sizes, vsize, i) { - char buffer[32]; - - if (vsize != NULL) { - int size_int = (intptr_t) vsize; - - sprintf(buffer, "%i", size_int); - - WMAddListItem(panel->sizLs, buffer); - } - } - - snprintf(asize, sizeof(asize) - 1, "%d", (int)(size + 0.5)); - - sz = WMFindRowOfListItemWithTitle(panel->sizLs, asize); - - if (sz < 0) { - return; - } - - WMSelectListItem(panel->sizLs, sz); - sizeClick(panel->sizLs, panel); - - return; -} diff --git a/WINGs/wings-rs-tests/examples/font_panel.rs b/WINGs/wings-rs-tests/examples/font_panel.rs index bbcf6104..d4cc380a 100644 --- a/WINGs/wings-rs-tests/examples/font_panel.rs +++ b/WINGs/wings-rs-tests/examples/font_panel.rs @@ -1,11 +1,11 @@ -use wings_rs::WINGsP::{WMGetFontPanel, WMScreenMainLoop, WMShowFontPanel}; +use wings_rs::{font_panel::FontPanel, WINGsP::WMScreenMainLoop}; use wings_rs_tests::LiveApplication; fn main() { let app = LiveApplication::new("WMFontPanel"); + let mut font_panel = FontPanel::new(app.screen.as_ptr()).expect("could not create font panel"); + font_panel.show(); unsafe { - let font_panel = WMGetFontPanel(app.screen.as_ptr()); - WMShowFontPanel(font_panel); WMScreenMainLoop(app.screen.as_ptr()); } } diff --git a/WINGs/wings-rs-tests/tests/font_panel_tests.rs b/WINGs/wings-rs-tests/tests/font_panel_tests.rs index 0e8f0a36..675e84a3 100644 --- a/WINGs/wings-rs-tests/tests/font_panel_tests.rs +++ b/WINGs/wings-rs-tests/tests/font_panel_tests.rs @@ -1,15 +1,14 @@ -use std::time::Instant; use insta_image::assert_png_snapshot; -use wings_rs::WINGsP::{WMGetFontPanel, WMShowFontPanel}; +use std::time::Instant; +use wings_rs::font_panel::FontPanel; use wings_rs_tests::HeadlessApplication; #[test] fn show_font_panel() { let mut app = HeadlessApplication::new(); - unsafe { - let font_panel = WMGetFontPanel(app.screen.as_ptr()); - WMShowFontPanel(font_panel); - } + let mut font_panel = + FontPanel::new(app.screen.as_ptr()).expect("could not construct font panel"); + font_panel.show(); while app.pump_event_queue(Instant::now()) {} assert_png_snapshot!("font_panel", app.xvfb.png_screenshot()); } diff --git a/WINGs/wings-rs/Makefile.am b/WINGs/wings-rs/Makefile.am index 3032b9b5..ea2e386b 100644 --- a/WINGs/wings-rs/Makefile.am +++ b/WINGs/wings-rs/Makefile.am @@ -5,6 +5,7 @@ RUST_SOURCES = \ src/button.rs \ src/configuration.rs \ src/font.rs \ + src/font_panel.rs \ src/lib.rs \ src/list.rs \ src/pango_extras.rs \ @@ -56,6 +57,9 @@ src/WINGsP.rs: ../WINGs/WINGsP.h ../../wrlib/wraster.h ../WINGs/WINGs.h ../WINGs -I../../wrlib \ -I.. && ./patch_WINGsP.sh src/WINGsP.rs +Cargo.lock: + $(CARGO) build + target/debug/libwings_rs.a: $(RUST_SOURCES) $(RUST_EXTRA) $(CARGO) build diff --git a/WINGs/wings-rs/src/font_panel.rs b/WINGs/wings-rs/src/font_panel.rs new file mode 100644 index 00000000..61c8a5ef --- /dev/null +++ b/WINGs/wings-rs/src/font_panel.rs @@ -0,0 +1,1019 @@ +//! Top-level window containing a font selector. + +use crate::{ + WINGsP::*, + font::{Font, FontName}, + widget::Widget, +}; + +use std::{ + collections::{BTreeMap, btree_map::Entry}, + ffi::{CStr, CString, c_int, c_void}, + pin::Pin, + ptr::{self, NonNull}, + rc::Rc, +}; + +/// Provides quick lookup of fonts that are known to exist. +pub struct FontLibrary { + families: BTreeMap, Family>, +} + +/// Tracks a set of known [`Typeface`] objects and a quick mapping from typeface names to `Typeface`s. +#[derive(Clone)] +pub struct Family { + name: Rc, + typefaces: BTreeMap, Rc>, +} + +/// Full name information for a typeface that is known to exist on this sytem. +#[derive(Clone)] +pub struct Typeface { + family: Rc, + name: Rc, + sizes: Vec, +} + +impl FontLibrary { + /// Creates a `FontLibrary` with all fonts known by the system. + /// + /// Small errors in listing fonts (like encountering a font name which is + /// not a valid UTF-8 byte sequence) are silently ignored (and those fonts + /// are skipped). + /// + /// If an unrecoverable error is encountered while listing system fonts, + /// returns `None`. + pub fn for_all_families() -> Option { + let pattern = unsafe { fontconfig_sys::FcPatternCreate() }; + if pattern.is_null() { + // TODO: complain. + return None; + } + let object_set = unsafe { + fontconfig_sys::FcObjectSetBuild( + fontconfig_sys::constants::FC_FAMILY.as_ptr() as *mut u8, + fontconfig_sys::constants::FC_STYLE.as_ptr(), + ptr::null_mut::(), + ) + }; + if object_set.is_null() { + // TODO: complain. + unsafe { + fontconfig_sys::FcPatternDestroy(pattern); + } + return None; + } + let fonts = unsafe { fontconfig_sys::FcFontList(ptr::null_mut(), pattern, object_set) }; + unsafe { + fontconfig_sys::FcPatternDestroy(pattern); + } + if fonts.is_null() { + // TODO: complain. + return None; + } + + let mut library = FontLibrary { + families: BTreeMap::new(), + }; + if unsafe { (*fonts).nfont } < 0 { + // TODO: complain. + return None; + } + for font in + unsafe { std::slice::from_raw_parts_mut((*fonts).fonts, (*fonts).nfont as usize) } + { + let mut family = ptr::null_mut(); + let mut style = ptr::null_mut(); + unsafe { + if fontconfig_sys::FcPatternGetString( + *font, + fontconfig_sys::constants::FC_FAMILY.as_ptr(), + 0, + &mut family as *mut _, + ) == fontconfig_sys::FcResultMatch + { + let Ok(family) = CStr::from_ptr(family).to_str().map(String::from) else { + // TODO: complain. + continue; + }; + if fontconfig_sys::FcPatternGetString( + *font, + fontconfig_sys::constants::FC_STYLE.as_ptr(), + 0, + &mut style as *mut _, + ) == fontconfig_sys::FcResultMatch + { + let Ok(style) = CStr::from_ptr(style).to_str().map(String::from) else { + // TODO: complain. + continue; + }; + library.register(family, style); + } + } + } + } + unsafe { + fontconfig_sys::FcFontSetDestroy(fonts); + } + + Some(library) + } + + /// Adds a font with the given family and typeface names to the fonts tracked by `self`. + pub fn register(&mut self, family: String, typeface: String) { + let family_name: Rc = family.into(); + let typeface_name: Rc = typeface.into(); + match self.families.entry(family_name.clone()) { + Entry::Occupied(mut o) => { + if let Entry::Vacant(v) = o.get_mut().typefaces.entry(typeface_name.clone()) { + v.insert(Rc::new(Typeface { + family: family_name, + name: typeface_name, + sizes: SCALABLE_FONT_SIZES.iter().copied().collect(), + })); + } + } + Entry::Vacant(v) => { + let name = v.key().clone(); + v.insert(Family { + name, + typefaces: [( + typeface_name.clone(), + Rc::new(Typeface { + family: family_name, + name: typeface_name, + sizes: SCALABLE_FONT_SIZES.iter().copied().collect(), + }), + )] + .into_iter() + .collect(), + }); + } + } + } + + /// Searches for a font family named `family`, returning `None` if it cannot + /// be found. + pub fn get_family(&self, family: &str) -> Option { + self.families.get(family).cloned() + } + + /// Searches for a typeface with the given `family` and `style`, returning + /// `None` if it cannot be found. + pub fn get_typeface(&self, family: &str, style: &str) -> Option> { + self.families + .get(family) + .and_then(|f| f.typefaces.get(style)) + .cloned() + } +} + +pub struct FontPanel { + fonts: FontLibrary, + + window: NonNull, + + split: NonNull, + + upper_frame: NonNull, + sample_text: NonNull, + + lower_frame: NonNull, + family_label: NonNull, + family_list: NonNull, + type_label: NonNull, + type_list: NonNull, + size_label: NonNull, + size_text: NonNull, + size_list: NonNull, + + action: Option>, + + set_button: NonNull, + revert_button: NonNull, +} + +impl FontPanel { + /// Constructs a font panel that shows all fonts known to the system. + /// + /// If there are errors in listing fonts or creating widgets, returns `None`. + /// + /// Call [`FontPanel::show`] to make the panel visible. + pub fn new(screen: *mut W_Screen) -> Option>> { + extern "C" fn size_click_callback(_widget: *mut WMWidget, data: *mut c_void) { + if data.is_null() { + return; + } + let panel = unsafe { &mut *data.cast::() }; + panel.update_size_label(); + panel.update_preview(); + } + + extern "C" fn typeface_click_callback(_widget: *mut WMWidget, data: *mut c_void) { + if data.is_null() { + return; + } + + let panel = unsafe { &mut *data.cast::() }; + panel.update_typeface_label(); + panel.update_preview(); + } + + extern "C" fn family_click(_widget: *mut WMWidget, data: *mut c_void) { + if data.is_null() { + return; + } + let panel = unsafe { &mut *data.cast::() }; + panel.handle_family_click(); + panel.update_preview(); + } + + extern "C" fn close_window_callback(_widget: *mut WMWidget, data: *mut c_void) { + unsafe { + ffi::WMHideFontPanel(data.cast()); + } + } + + extern "C" fn split_view_constrain_callback( + _split_view: *mut W_SplitView, + view_index: c_int, + min: *mut c_int, + _max: *mut c_int, + ) { + unsafe { + *min = if view_index == 0 { + MIN_UPPER_HEIGHT as i32 + } else { + MIN_LOWER_HEIGHT as i32 + }; + } + } + + extern "C" fn set_clicked_action(_widget: *mut WMWidget, data: *mut c_void) { + let panel = unsafe { &mut *data.cast::() }; + if let Some(ref mut f) = panel.action { + (*f)() + } + } + + extern "C" fn revert_clicked_action(_widget: *mut WMWidget, _data: *mut c_void) {} + + let screen = NonNull::new(screen).map(|s| unsafe { &mut *s.as_ptr() })?; + let fonts = FontLibrary::for_all_families()?; + let window = NonNull::new(unsafe { WMCreateWindow(screen, c"fontPanel".as_ptr()) })?; + let split = NonNull::new(unsafe { WMCreateSplitView(window.as_ptr().cast()) })?; + let upper_frame = NonNull::new(unsafe { WMCreateFrame(window.as_ptr().cast()) })?; + let sample_text = NonNull::new(unsafe { WMCreateTextField(upper_frame.as_ptr().cast()) })?; + let lower_frame = NonNull::new(unsafe { WMCreateFrame(window.as_ptr().cast()) })?; + let family_label = NonNull::new(unsafe { WMCreateLabel(lower_frame.as_ptr().cast()) })?; + let family_list = NonNull::new(unsafe { WMCreateList(lower_frame.as_ptr().cast()) })?; + let type_label = NonNull::new(unsafe { WMCreateLabel(lower_frame.as_ptr().cast()) })?; + let type_list = NonNull::new(unsafe { WMCreateList(lower_frame.as_ptr().cast()) })?; + let size_label = NonNull::new(unsafe { WMCreateLabel(lower_frame.as_ptr().cast()) })?; + let size_text = NonNull::new(unsafe { WMCreateTextField(lower_frame.as_ptr().cast()) })?; + let size_list = NonNull::new(unsafe { WMCreateList(lower_frame.as_ptr().cast()) })?; + let set_button = NonNull::new(unsafe { + crate::button::ffi::WMCreateCommandButton(window.as_ptr().cast()) + })?; + let revert_button = NonNull::new(unsafe { + crate::button::ffi::WMCreateCommandButton(window.as_ptr().cast()) + })?; + + let mut panel = Box::new(FontPanel { + fonts, + window, + split, + upper_frame, + sample_text, + lower_frame, + family_label, + family_list, + type_label, + type_list, + size_label, + size_text, + size_list, + + action: None, + set_button, + revert_button, + }); + + unsafe { + // TODO: i18n. + WMSetWindowTitle(window.as_ptr(), c"Font Panel".as_ptr()); + WMResizeWidget(window.as_ptr().cast(), DEF_WIDTH, DEF_HEIGHT); + WMSetWindowMinSize(window.as_ptr().cast(), MIN_WIDTH, MIN_HEIGHT); + WMSetViewNotifySizeChanges((*window.as_ptr()).view().as_ptr(), 1); + WMSetWindowCloseAction( + window.as_ptr(), + Some(close_window_callback), + panel.as_mut() as *mut _ as *mut c_void, + ); + } + + unsafe { + WMResizeWidget( + split.as_ptr().cast(), + DEF_WIDTH, + DEF_HEIGHT - BUTTON_SPACE_HEIGHT, + ); + WMSetSplitViewConstrainProc(split.as_ptr(), Some(split_view_constrain_callback)); + } + + let div_width = unsafe { WMGetSplitViewDividerThickness(split.as_ptr()) }; + + unsafe { + WMSetFrameRelief(upper_frame.as_ptr(), WMReliefType_WRFlat); + WMSetViewNotifySizeChanges((*upper_frame.as_ptr()).view().as_ptr(), 1); + } + + unsafe { + WMSetFrameRelief(lower_frame.as_ptr(), WMReliefType_WRFlat); + WMSetViewNotifySizeChanges((*lower_frame.as_ptr()).view().as_ptr(), 1); + + WMAddSplitViewSubview(split.as_ptr(), (*upper_frame.as_ptr()).view().as_ptr()); + WMAddSplitViewSubview(split.as_ptr(), (*lower_frame.as_ptr()).view().as_ptr()); + + WMResizeWidget(upper_frame.as_ptr().cast(), DEF_WIDTH, DEF_UPPER_HEIGHT); + WMResizeWidget(lower_frame.as_ptr().cast(), DEF_WIDTH, DEF_LOWER_HEIGHT); + + WMMoveWidget(lower_frame.as_ptr().cast(), 0, 60 + div_width); + } + + let white = unsafe { WMWhiteColor(screen) }; + let dark = unsafe { WMDarkGrayColor(screen) }; + + unsafe { + WMResizeWidget(sample_text.as_ptr().cast(), DEF_WIDTH - 20, 50); + WMMoveWidget(sample_text.as_ptr().cast(), 10, 10); + // TODO: i18n. + WMSetTextFieldText( + sample_text.as_ptr(), + c"The quick brown fox jumps over the lazy dog".as_ptr(), + ); + } + + let font = unsafe { crate::font::ffi::WMBoldSystemFontOfSize(screen, 12) }; + + unsafe { + WMSetWidgetBackgroundColor(family_label.as_ptr().cast(), dark); + // TODO: i18n. + WMSetLabelText(family_label.as_ptr(), c"Family".as_ptr()); + WMSetLabelFont(family_label.as_ptr(), font); + WMSetLabelTextColor(family_label.as_ptr(), white); + WMSetLabelRelief(family_label.as_ptr(), WMReliefType_WRSunken); + WMSetLabelTextAlignment(family_label.as_ptr(), WMAlignment_WACenter); + } + + unsafe { + WMSetListAction( + family_list.as_ptr(), + Some(family_click), + panel.as_mut() as *mut FontPanel as *mut _, + ); + } + + unsafe { + WMSetWidgetBackgroundColor(type_label.as_ptr().cast(), dark); + // TODO: i18n. + WMSetLabelText(type_label.as_ptr(), c"Typeface".as_ptr()); + WMSetLabelFont(type_label.as_ptr(), font); + WMSetLabelTextColor(type_label.as_ptr(), white); + WMSetLabelRelief(type_label.as_ptr(), WMReliefType_WRSunken); + WMSetLabelTextAlignment(type_label.as_ptr(), WMAlignment_WACenter); + } + + unsafe { + WMSetListAction( + type_list.as_ptr(), + Some(typeface_click_callback), + panel.as_mut() as *mut FontPanel as *mut _, + ); + } + + unsafe { + WMSetWidgetBackgroundColor(size_label.as_ptr().cast(), dark); + // TODO: i18n. + WMSetLabelText(size_label.as_ptr().cast(), c"Size".as_ptr()); + WMSetLabelFont(size_label.as_ptr().cast(), font); + WMSetLabelTextColor(size_label.as_ptr().cast(), white); + WMSetLabelRelief(size_label.as_ptr().cast(), WMReliefType_WRSunken); + WMSetLabelTextAlignment(size_label.as_ptr().cast(), WMAlignment_WACenter); + } + + unsafe { + WMSetListAction( + size_list.as_ptr(), + Some(size_click_callback), + panel.as_mut() as *mut FontPanel as *mut _, + ); + } + + unsafe { + crate::font::ffi::WMReleaseFont(font); + WMReleaseColor(white); + WMReleaseColor(dark); + } + + unsafe { + WMResizeWidget(set_button.as_ptr().cast(), 70, 24); + WMMoveWidget( + set_button.as_ptr().cast(), + 240, + (DEF_HEIGHT - (BUTTON_SPACE_HEIGHT - 5)) as i32, + ); + // TODO: i18n. + WMSetButtonText(set_button.as_ptr().cast(), c"Set".as_ptr()); + WMSetButtonAction( + set_button.as_ptr().cast(), + Some(set_clicked_action), + panel.as_mut() as *mut FontPanel as *mut _, + ); + } + + unsafe { + WMResizeWidget(revert_button.as_ptr().cast(), 70, 24); + WMMoveWidget( + revert_button.as_ptr().cast(), + 80, + (DEF_HEIGHT - (BUTTON_SPACE_HEIGHT - 5)) as i32, + ); + // TODO: i18n. + WMSetButtonText(revert_button.as_ptr().cast(), c"Revert".as_ptr()); + WMSetButtonAction( + revert_button.as_ptr().cast(), + Some(revert_clicked_action), + panel.as_mut() as *mut FontPanel as *mut _, + ); + } + + unsafe { + WMRealizeWidget(window.as_ptr().cast()); + + WMMapSubwidgets(upper_frame.as_ptr().cast()); + WMMapSubwidgets(lower_frame.as_ptr().cast()); + WMMapSubwidgets(split.as_ptr().cast()); + WMMapSubwidgets(window.as_ptr().cast()); + + WMUnmapWidget(revert_button.as_ptr().cast()); + } + + panel.arrange_lower_frame(); + + extern "C" fn notification_observer(this: *mut c_void, notification: *mut WMNotification) { + if this.is_null() || notification.is_null() { + return; + } + let panel = unsafe { &mut *this.cast::() }; + let object = unsafe { WMGetNotificationObject(notification) }; + let window = (*panel).window; + + let name = unsafe { CStr::from_ptr(WMGetNotificationName(notification)) }; + if name == unsafe { CStr::from_ptr(WMViewSizeDidChangeNotification) } { + if ptr::addr_eq(object, window.as_ptr()) { + unsafe { + let h = WMWidgetHeight(window.as_ptr().cast()); + let w = WMWidgetWidth(window.as_ptr().cast()); + WMResizeWidget(panel.split.as_ptr().cast(), w, h - BUTTON_SPACE_HEIGHT); + WMMoveWidget( + panel.set_button.as_ptr().cast(), + (w - 80) as i32, + (h - (BUTTON_SPACE_HEIGHT - 5)) as i32, + ); + WMMoveWidget( + panel.revert_button.as_ptr().cast(), + (w - 240) as i32, + (h - (BUTTON_SPACE_HEIGHT - 5)) as i32, + ); + } + } else if ptr::addr_eq(object, unsafe { (*panel.upper_frame.as_ptr()).view().as_ptr() }) + { + unsafe { + if WMWidgetHeight(panel.upper_frame.as_ptr().cast()) < MIN_UPPER_HEIGHT { + WMResizeWidget( + panel.upper_frame.as_ptr().cast(), + WMWidgetWidth(panel.upper_frame.as_ptr().cast()), + MIN_UPPER_HEIGHT, + ); + } else { + WMResizeWidget( + panel.sample_text.as_ptr().cast(), + WMWidgetWidth(panel.upper_frame.as_ptr().cast()) - 20, + WMWidgetHeight(panel.upper_frame.as_ptr().cast()) - 10, + ); + } + } + } else if ptr::addr_eq(object, unsafe { (*panel.lower_frame.as_ptr()).view().as_ptr() }) + { + unsafe { + if WMWidgetHeight(panel.lower_frame.as_ptr().cast()) < MIN_LOWER_HEIGHT { + WMResizeWidget( + panel.upper_frame.as_ptr().cast(), + WMWidgetWidth(panel.upper_frame.as_ptr().cast()), + MIN_UPPER_HEIGHT, + ); + WMMoveWidget( + panel.lower_frame.as_ptr().cast(), + 0, + WMWidgetHeight(panel.upper_frame.as_ptr().cast()) as i32 + + WMGetSplitViewDividerThickness(panel.split.as_ptr()) as i32, + ); + WMResizeWidget( + panel.lower_frame.as_ptr().cast(), + WMWidgetWidth(panel.lower_frame.as_ptr().cast()) as u32, + WMWidgetWidth(panel.split.as_ptr().cast()) + - MIN_UPPER_HEIGHT as u32 + - WMGetSplitViewDividerThickness(panel.split.as_ptr().cast()) + as u32, + ); + } else { + panel.arrange_lower_frame(); + } + } + } + } + } + + unsafe { + WMAddNotificationObserver( + Some(notification_observer), + panel.as_mut() as *mut FontPanel as *mut _, + WMViewSizeDidChangeNotification, + (*window.as_ptr()).view().as_ptr().cast(), + ); + WMAddNotificationObserver( + Some(notification_observer), + panel.as_mut() as *mut FontPanel as *mut _, + WMViewSizeDidChangeNotification, + (*upper_frame.as_ptr()).view().as_ptr().cast(), + ); + WMAddNotificationObserver( + Some(notification_observer), + panel.as_mut() as *mut FontPanel as *mut _, + WMViewSizeDidChangeNotification, + (*lower_frame.as_ptr()).view().as_ptr().cast(), + ); + } + + // Add font families to the list. + for family in panel.fonts.families.values() { + let Ok(name) = CString::new(&*family.name) else { + continue; + }; + unsafe { + let item = crate::list::ffi::WMAddListItem(panel.family_list.as_ptr(), name.as_ptr()); + if !item.is_null() { + (*item).clientData = family as *const Family as *mut _; + } + } + } + unsafe { + WMSortListItems(panel.family_list.as_ptr()); + } + + Some(Pin::new(panel)) + } + + fn arrange_lower_frame(&mut self) { + const LABEL_HEIGHT: u32 = 20; + + let width = unsafe { WMWidgetWidth(self.lower_frame.as_ptr().cast()) } - 55 - 30; + let height = unsafe { + WMWidgetHeight(self.split.as_ptr().cast()) + - WMWidgetHeight(self.upper_frame.as_ptr().cast()) + - WMGetSplitViewDividerThickness(self.split.as_ptr().cast()) as u32 + - LABEL_HEIGHT + - 8 + }; + + let fw: u32 = (125 * width as u32) / 235; + let tw: u32 = (110 * width as u32) / 235; + let sw: u32 = 55; + + unsafe { + WMMoveWidget(self.family_label.as_ptr().cast(), 10, 0); + WMResizeWidget(self.family_label.as_ptr().cast(), fw, LABEL_HEIGHT); + WMMoveWidget(self.family_list.as_ptr().cast(), 10, 23); + WMResizeWidget(self.family_list.as_ptr().cast(), fw, height); + + WMMoveWidget(self.type_label.as_ptr().cast(), 10 + (fw as i32) + 3, 0); + WMResizeWidget(self.type_label.as_ptr().cast(), tw, LABEL_HEIGHT); + + WMMoveWidget(self.type_list.as_ptr().cast(), 10 + (fw as i32) + 3, 23); + WMResizeWidget(self.type_list.as_ptr().cast(), tw, height); + + WMMoveWidget( + self.size_label.as_ptr().cast(), + 10 + (fw as i32) + 3 + (tw as i32) + 3, + 0, + ); + WMResizeWidget(self.size_label.as_ptr().cast(), sw + 4, LABEL_HEIGHT); + + WMMoveWidget( + self.size_text.as_ptr().cast(), + 10 + (fw as i32) + 3 + (tw as i32) + 3, + 23, + ); + WMResizeWidget(self.size_text.as_ptr().cast(), sw + 4, 20); + + WMMoveWidget( + self.size_list.as_ptr().cast(), + 10 + (fw as i32) + 3 + (tw as i32) + 3, + 46, + ); + WMResizeWidget(self.size_list.as_ptr().cast(), sw + 4, height - 23); + } + } + + fn handle_family_click(&mut self) { + let Some(family) = (unsafe { + NonNull::new(WMGetListSelectedItem(self.family_list.as_ptr())) + .and_then(|item| NonNull::new((*item.as_ptr()).clientData.cast::())) + .map(|family| (*family.as_ptr()).clone()) + }) else { + return; + }; + // Hang onto the selected typeface and size so we can try to keep them + // selected for the new family. + let selected_type = unsafe { + NonNull::new(WMGetListSelectedItem(self.type_list.as_ptr())) + .and_then(|item| NonNull::new((*item.as_ptr()).clientData.cast::())) + .map(|typeface| (*typeface.as_ptr()).clone()) + }; + let selected_size = unsafe { + NonNull::new(WMGetListSelectedItem(self.size_list.as_ptr())) + .and_then(|item| NonNull::new((*item.as_ptr()).text)) + .map(|text| CString::from(CStr::from_ptr(text.as_ptr()))) + }; + + unsafe { + WMClearList(self.type_list.as_ptr()); + } + for face in family.typefaces.values() { + let Ok(label) = CString::new(&*face.name) else { + continue; + }; + unsafe { + let item = if face.name.eq_ignore_ascii_case("Roman") + || face.name.eq_ignore_ascii_case("Regular") + { + WMInsertListItem(self.type_list.as_ptr(), 0, label.as_ptr()) + } else { + crate::list::ffi::WMAddListItem(self.type_list.as_ptr(), label.as_ptr()) + }; + (*item).clientData = Rc::::as_ptr(face) as *mut _ + } + } + + let face_index = selected_type + .map(|x| unsafe { + WMFindRowOfListItemWithTitle(self.type_list.as_ptr(), x.name.as_ptr()) + }) + .unwrap_or(0); + + unsafe { + WMSelectListItem(self.type_list.as_ptr(), face_index); + } + let size_index = selected_size + .map(|x| unsafe { + WMFindRowOfListItemWithTitle(self.size_list.as_ptr(), x.as_ptr()) + }) + .unwrap_or(0); + unsafe { + WMSelectListItem(self.size_list.as_ptr(), size_index); + } + self.update_typeface_label(); + } + + fn update_typeface_label(&mut self) { + let selected_size_text = unsafe { + NonNull::new(WMGetTextFieldText(self.size_text.as_ptr())) + .map(|text| CString::from(CStr::from_ptr(text.as_ptr()))) + }; + + unsafe { + WMClearList(self.size_list.as_ptr() as *mut _); + } + + if let Some(selected_typeface_sizes) = + NonNull::new(unsafe { WMGetListSelectedItem(self.type_list.as_ptr()) }) + .and_then(|item| { + NonNull::new(unsafe { (*item.as_ptr()).clientData.cast::() }) + }) + .map(|typeface| unsafe { &(*typeface.as_ptr()).sizes }) + { + for size in selected_typeface_sizes { + let Ok(item_text) = CString::new(format!("{}", size)) else { + continue; + }; + unsafe { + crate::list::ffi::WMAddListItem(self.size_list.as_ptr(), item_text.as_ptr()); + } + } + } + + let mut new_size_index = 0; + if let Some(size) = selected_size_text { + new_size_index = + unsafe { WMFindRowOfListItemWithTitle(self.size_list.as_ptr(), size.as_ptr()) }; + } + if new_size_index < 0 { + new_size_index = + unsafe { WMFindRowOfListItemWithTitle(self.size_list.as_ptr(), c"12".as_ptr()) }; + } + if new_size_index < 0 { + new_size_index = 0; + } + + unsafe { + WMSelectListItem(self.size_list.as_ptr(), new_size_index); + WMSetListPosition(self.size_list.as_ptr(), new_size_index); + } + + self.update_size_label(); + } + + fn update_size_label(&mut self) { + unsafe { + let item = WMGetListSelectedItem(self.size_list.as_ptr()); + if item.is_null() { + return; + } + WMSetTextFieldText(self.size_text.as_ptr(), (*item).text); + let Ok(len) = CStr::from_ptr((*item).text).count_bytes().try_into() else { + return; + }; + WMSelectTextFieldRange(self.size_text.as_ptr(), wmkrange(0, len)); + } + } + + pub fn selected_font_name(&self) -> Option { + let typeface = unsafe { + NonNull::new(WMGetListSelectedItem(self.type_list.as_ptr())) + .and_then(|item| NonNull::new((*item.as_ptr()).clientData.cast::())) + .map(|typeface| (*typeface.as_ptr()).clone())? + }; + + let size = unsafe { + NonNull::new(WMGetTextFieldText(self.size_text.as_ptr())) + .and_then(|text| CStr::from_ptr(text.as_ptr()).to_str().ok())? + }; + + FontName::new(&format!( + "{}:style={}:pixelsize={}", + typeface.family, typeface.name, size + )) + } + + fn update_preview(&mut self) { + let Some(name) = self.selected_font_name() else { + return; + }; + let screen = unsafe { &mut *WMWidgetScreen(self.window.as_ptr().cast()) }; + let x_display = unsafe { &mut *(screen.display) }; + let x_screen = screen.screen; + let Some(mut font) = + screen.font_cache_get_or_else(name, |name| Font::load(x_display, x_screen, name)) + else { + return; + }; + unsafe { + WMSetTextFieldFont(self.sample_text.as_ptr(), &mut font as *mut _); + } + } + + pub fn set_font_name(&mut self, family: &CStr, style: &CStr, size: f64) { + let family_row = + unsafe { WMFindRowOfListItemWithTitle(self.family_list.as_ptr(), family.as_ptr()) }; + if family_row < 0 { + return; + } + unsafe { + WMSelectListItem(self.family_list.as_ptr(), family_row); + WMSetListPosition(self.family_list.as_ptr(), family_row); + WMClearList(self.type_list.as_ptr()); + } + + let item = unsafe { WMGetListSelectedItem(self.family_list.as_ptr()) }; + if let Some(family) = NonNull::new(item) + .and_then(|item| NonNull::new(unsafe { *item.as_ptr() }.clientData.cast::())) + { + for face in unsafe { (*family.as_ptr()).typefaces.values() } { + let Ok(face_name) = CString::new(&*face.name) else { + continue; + }; + let face_item = unsafe { + if face.name.eq_ignore_ascii_case("Roman") { + WMInsertListItem(self.type_list.as_ptr(), 0, face_name.as_ptr()) + } else { + crate::list::ffi::WMAddListItem(self.type_list.as_ptr(), face_name.as_ptr()) + } + }; + unsafe { + (*face_item).clientData = Rc::::as_ptr(face) as *mut _; + } + } + } + + let style_row = + unsafe { WMFindRowOfListItemWithTitle(self.type_list.as_ptr(), style.as_ptr()) }; + + if style_row < 0 { + return; + } + + unsafe { + WMSelectListItem(self.type_list.as_ptr(), style_row); + } + let item = unsafe { WMGetListSelectedItem(self.type_list.as_ptr()) }; + let face = unsafe { (*item).clientData.cast::() }; + + unsafe { + WMClearList(self.size_list.as_ptr()); + } + if !face.is_null() { + for size in unsafe { &(*face).sizes } { + if let Ok(size) = CString::new(format!("{}", size)) { + unsafe { + crate::list::ffi::WMAddListItem(self.size_list.as_ptr(), size.as_ptr()); + } + } + } + } + + if let Ok(asize) = CString::new(format!("{}", (size + 0.5) as u32)) { + let sz = + unsafe { WMFindRowOfListItemWithTitle(self.size_list.as_ptr(), asize.as_ptr()) }; + if sz < 0 { + return; + } + unsafe { + WMSelectListItem(self.size_list.as_ptr(), sz); + } + } + + self.update_size_label(); + self.update_preview(); + } + + pub fn show(&mut self) { + unsafe { + WMMapWidget(self.window.as_ptr().cast()); + } + } +} + +impl Drop for FontPanel { + fn drop(&mut self) { + unsafe { + WMRemoveNotificationObserver(self as *mut FontPanel as *mut _); + WMUnmapWidget(self.window.as_ptr().cast()); + WMDestroyWidget(self.window.as_ptr().cast()); + } + } +} + +const MIN_UPPER_HEIGHT: u32 = 20; +const MIN_LOWER_HEIGHT: u32 = 140; + +const BUTTON_SPACE_HEIGHT: u32 = 40; + +const MIN_WIDTH: u32 = 250; +const MIN_HEIGHT: u32 = MIN_UPPER_HEIGHT + MIN_LOWER_HEIGHT + BUTTON_SPACE_HEIGHT; + +const DEF_UPPER_HEIGHT: u32 = 60; +const DEF_LOWER_HEIGHT: u32 = 310; + +const DEF_WIDTH: u32 = 320; +const DEF_HEIGHT: u32 = DEF_UPPER_HEIGHT + DEF_LOWER_HEIGHT; + +const SCALABLE_FONT_SIZES: [u32; 12] = [8, 10, 11, 12, 14, 16, 18, 20, 24, 36, 48, 64]; + +pub mod ffi { + use super::FontPanel; + use crate::{ + WINGsP::*, + font::{FontName, ffi::WMFont}, + }; + + use std::{ + ffi::{CStr, c_char, c_void}, + pin::Pin, + ptr, + }; + + pub type WMFontPanel = Pin>; + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreateFontPanel(screen: *mut W_Screen) -> *mut WMFontPanel { + if screen.is_null() { + return ptr::null_mut(); + } + + let screen = unsafe { &mut *screen }; + if let Some(panel) = FontPanel::new(screen) { + Box::leak(Box::new(panel)) as *mut WMFontPanel + } else { + ptr::null_mut() + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMShowFontPanel(panel: *mut FontPanel) { + if panel.is_null() { + return; + } + let panel = unsafe { &mut *panel }; + panel.show(); + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMHideFontPanel(panel: *mut FontPanel) { + unsafe { WMUnmapWidget((*panel).window.as_ptr().cast()) } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMFreeFontPanel(panel: *mut FontPanel) { + if panel.is_null() { + return; + } + let _ = unsafe { Box::from_raw(panel) }; + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMSetFontPanelAction( + panel: *mut FontPanel, + action: Option, + data: *mut c_void, + ) { + if panel.is_null() { + return; + } + let panel = unsafe { &mut *panel }; + if let Some(f) = action { + let panel_ptr = panel as *mut _ as *mut c_void; + panel.action = Some(Box::new(move || unsafe { (f)(panel_ptr, data) })); + } else { + panel.action = None; + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMSetFontPanelFont(panel: *mut FontPanel, font_name: *const c_char) { + if panel.is_null() || font_name.is_null() { + return; + } + let panel = unsafe { &mut *panel }; + let Some(name) = (unsafe { CStr::from_ptr(font_name).to_str() }) + .ok() + .and_then(|n| FontName::new(n)) + else { + return; + }; + + unsafe { + let pattern = fontconfig_sys::FcNameParse(name.as_xft_name()); + if pattern.is_null() { + return; + } + let mut family = ptr::null_mut(); + let mut style = ptr::null_mut(); + let mut size = 0.0; + if fontconfig_sys::FcPatternGetString( + pattern, + fontconfig_sys::constants::FC_FAMILY.as_ptr(), + 0, + &mut family, + ) == fontconfig_sys::FcResultMatch + { + if fontconfig_sys::FcPatternGetString( + pattern, + fontconfig_sys::constants::FC_STYLE.as_ptr(), + 0, + &mut style, + ) == fontconfig_sys::FcResultMatch + { + if fontconfig_sys::FcPatternGetDouble( + pattern, + c"pixelsize".as_ptr(), + 0, + &mut size, + ) == fontconfig_sys::FcResultMatch + { + panel.set_font_name(CStr::from_ptr(family), CStr::from_ptr(style), size); + } + } + } + fontconfig_sys::FcPatternDestroy(pattern); + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMGetFontPanelFont(panel: *mut FontPanel) -> *mut WMFont { + if panel.is_null() { + return ptr::null_mut(); + } + return unsafe { WMGetTextFieldFont((*panel).sample_text.as_ptr()) }; + } +} diff --git a/WINGs/wings-rs/src/lib.rs b/WINGs/wings-rs/src/lib.rs index 1609fe1d..02464da9 100644 --- a/WINGs/wings-rs/src/lib.rs +++ b/WINGs/wings-rs/src/lib.rs @@ -6,6 +6,7 @@ pub mod WINGsP; pub mod button; pub mod configuration; pub mod font; +pub mod font_panel; pub mod list; pub(crate) mod pango_extras; pub mod screen; -- 2.39.5