From f0c17ffc0dc0f45ac3433ed9b79b67734ba64a06 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Fri, 12 Jun 2015 23:14:38 +1000 Subject: [PATCH] Update to v094r24 release. byuu says: Finally!! Compilation works once again on Windows. However, it's pretty buggy. Modality isn't really working right, you can still poke at other windows, but when you select ListView items, they redraw as empty boxes (need to process WM_DRAWITEM before checking modality.) The program crashes when you close it (probably a ruby driver's term() function, that's what it usually is.) The Layout::setEnabled(false) call isn't working right, so you get that annoying chiming sound and cursor movement when mapping keyboard keys to game inputs. The column sizing seems off a bit on first display for the Hotkeys tab. And probably lots more. --- GNUmakefile | 9 +- data/resource.rc | 2 + emulator/emulator.hpp | 2 +- hiro/components.hpp | 10 +- hiro/core/action/menu-radio-item.cpp | 34 +- hiro/core/core.cpp | 4 + hiro/core/core.hpp | 128 +++- hiro/core/group.cpp | 39 + hiro/core/object.cpp | 26 + hiro/core/widget/combo-button-item.cpp | 8 +- hiro/core/widget/combo-button.cpp | 17 +- hiro/core/widget/list-view-cell.cpp | 49 ++ hiro/core/widget/list-view-column.cpp | 22 +- hiro/core/widget/list-view-item.cpp | 73 +- hiro/core/widget/list-view.cpp | 73 +- hiro/core/widget/radio-button.cpp | 34 +- hiro/core/widget/radio-label.cpp | 34 +- hiro/core/widget/tab-frame-item.cpp | 12 +- hiro/core/widget/tab-frame.cpp | 5 +- hiro/core/window.cpp | 2 + hiro/extension/browser-dialog.cpp | 38 +- hiro/extension/shared.hpp | 93 ++- hiro/gtk/action/menu-radio-item.cpp | 77 +- hiro/gtk/action/menu-radio-item.hpp | 8 +- hiro/gtk/font.cpp | 18 +- hiro/gtk/font.hpp | 18 +- hiro/gtk/group.cpp | 13 + hiro/gtk/group.hpp | 11 + hiro/gtk/keyboard.cpp | 2 +- hiro/gtk/platform.cpp | 4 + hiro/gtk/platform.hpp | 6 +- hiro/gtk/utility.cpp | 2 + hiro/gtk/widget/combo-button-item.cpp | 15 +- hiro/gtk/widget/combo-button-item.hpp | 2 +- hiro/gtk/widget/combo-button.cpp | 36 +- hiro/gtk/widget/list-view-cell.cpp | 40 + hiro/gtk/widget/list-view-cell.hpp | 18 + hiro/gtk/widget/list-view-column.cpp | 10 +- hiro/gtk/widget/list-view-column.hpp | 2 +- hiro/gtk/widget/list-view-item.cpp | 25 +- hiro/gtk/widget/list-view-item.hpp | 6 +- hiro/gtk/widget/list-view.cpp | 189 +++-- hiro/gtk/widget/list-view.hpp | 12 +- hiro/gtk/widget/radio-button.cpp | 52 +- hiro/gtk/widget/radio-button.hpp | 4 +- hiro/gtk/widget/radio-label.cpp | 57 +- hiro/gtk/widget/radio-label.hpp | 7 +- hiro/gtk/widget/tab-frame-item.cpp | 6 + hiro/gtk/widget/tab-frame-item.hpp | 2 + hiro/gtk/widget/tab-frame.cpp | 22 +- hiro/gtk/window.cpp | 14 +- hiro/gtk/window.hpp | 10 +- hiro/windows/action/action.cpp | 46 +- hiro/windows/action/action.hpp | 21 + hiro/windows/action/check-item.cpp | 24 - hiro/windows/action/item.cpp | 37 - hiro/windows/action/menu-check-item.cpp | 29 + hiro/windows/action/menu-check-item.hpp | 16 + hiro/windows/action/menu-item.cpp | 39 + hiro/windows/action/menu-item.hpp | 20 + hiro/windows/action/menu-radio-item.cpp | 49 ++ hiro/windows/action/menu-radio-item.hpp | 17 + hiro/windows/action/menu-separator.cpp | 13 + hiro/windows/action/menu-separator.hpp | 11 + hiro/windows/action/menu.cpp | 169 +++-- hiro/windows/action/menu.hpp | 22 + hiro/windows/action/radio-item.cpp | 32 - hiro/windows/action/separator.cpp | 10 - hiro/windows/application.cpp | 167 ++-- hiro/windows/application.hpp | 16 + hiro/windows/browser-window.cpp | 20 +- hiro/windows/browser-window.hpp | 13 + hiro/windows/desktop.cpp | 10 +- hiro/windows/desktop.hpp | 12 + hiro/windows/font.cpp | 24 +- hiro/windows/font.hpp | 18 + hiro/windows/group.cpp | 13 + hiro/windows/group.hpp | 11 + hiro/windows/header.hpp | 40 +- .../{phoenix.Manifest => hiro.Manifest} | 2 +- hiro/windows/hiro.rc | 1 + hiro/windows/hotkey.cpp | 13 + hiro/windows/hotkey.hpp | 11 + hiro/windows/keyboard.cpp | 215 +++--- hiro/windows/keyboard.hpp | 18 + hiro/windows/layout.cpp | 22 + hiro/windows/layout.hpp | 15 + hiro/windows/menu-bar.cpp | 83 ++ hiro/windows/menu-bar.hpp | 23 + hiro/windows/message-window.cpp | 26 +- hiro/windows/message-window.hpp | 14 + hiro/windows/monitor.cpp | 14 +- hiro/windows/monitor.hpp | 13 + hiro/windows/mouse.cpp | 12 +- hiro/windows/mouse.hpp | 12 + hiro/windows/object.cpp | 44 +- hiro/windows/object.hpp | 31 + hiro/windows/phoenix.rc | 1 - hiro/windows/platform.cpp | 26 +- hiro/windows/platform.hpp | 710 ++---------------- hiro/windows/popup-menu.cpp | 109 +++ hiro/windows/popup-menu.hpp | 19 + hiro/windows/settings.cpp | 5 - hiro/windows/sizable.cpp | 19 + hiro/windows/sizable.hpp | 14 + hiro/windows/status-bar.cpp | 54 ++ hiro/windows/status-bar.hpp | 21 + hiro/windows/timer.cpp | 34 +- hiro/windows/timer.hpp | 16 + hiro/windows/utility.cpp | 465 ++++++------ hiro/windows/widget/button.cpp | 144 ++-- hiro/windows/widget/button.hpp | 24 + hiro/windows/widget/canvas.cpp | 226 +++--- hiro/windows/widget/canvas.hpp | 31 + hiro/windows/widget/check-button.cpp | 146 ++-- hiro/windows/widget/check-button.hpp | 25 + hiro/windows/widget/check-label.cpp | 54 +- hiro/windows/widget/check-label.hpp | 17 + hiro/windows/widget/combo-button-item.cpp | 42 ++ hiro/windows/widget/combo-button-item.hpp | 17 + hiro/windows/widget/combo-button.cpp | 110 ++- hiro/windows/widget/combo-button.hpp | 19 + hiro/windows/widget/console.cpp | 56 -- hiro/windows/widget/frame.cpp | 80 +- hiro/windows/widget/frame.hpp | 16 + hiro/windows/widget/hex-edit.cpp | 166 ++-- hiro/windows/widget/hex-edit.hpp | 29 + hiro/windows/widget/horizontal-scroller.cpp | 55 +- hiro/windows/widget/horizontal-scroller.hpp | 17 + hiro/windows/widget/horizontal-slider.cpp | 58 +- hiro/windows/widget/horizontal-slider.hpp | 17 + hiro/windows/widget/label.cpp | 65 +- hiro/windows/widget/label.hpp | 16 + hiro/windows/widget/line-edit.cpp | 80 +- hiro/windows/widget/line-edit.hpp | 23 + hiro/windows/widget/list-view-cell.cpp | 49 ++ hiro/windows/widget/list-view-cell.hpp | 19 + hiro/windows/widget/list-view-column.cpp | 81 ++ hiro/windows/widget/list-view-column.hpp | 28 + hiro/windows/widget/list-view-item.cpp | 57 ++ hiro/windows/widget/list-view-item.hpp | 21 + hiro/windows/widget/list-view.cpp | 648 +++++++++------- hiro/windows/widget/list-view.hpp | 47 ++ hiro/windows/widget/progress-bar.cpp | 33 +- hiro/windows/widget/progress-bar.hpp | 14 + hiro/windows/widget/radio-button.cpp | 162 ++-- hiro/windows/widget/radio-button.hpp | 26 + hiro/windows/widget/radio-label.cpp | 72 +- hiro/windows/widget/radio-label.hpp | 18 + hiro/windows/widget/tab-frame-item.cpp | 63 ++ hiro/windows/widget/tab-frame-item.hpp | 21 + hiro/windows/widget/tab-frame.cpp | 192 ++--- hiro/windows/widget/tab-frame.hpp | 27 + hiro/windows/widget/text-edit.cpp | 87 ++- hiro/windows/widget/text-edit.hpp | 23 + hiro/windows/widget/vertical-scroller.cpp | 55 +- hiro/windows/widget/vertical-scroller.hpp | 17 + hiro/windows/widget/vertical-slider.cpp | 58 +- hiro/windows/widget/vertical-slider.hpp | 17 + hiro/windows/widget/viewport.cpp | 76 +- hiro/windows/widget/viewport.hpp | 14 + hiro/windows/widget/widget.cpp | 106 +-- hiro/windows/widget/widget.hpp | 28 + hiro/windows/window.cpp | 491 +++++------- hiro/windows/window.hpp | 47 ++ nall/image/base.hpp | 8 +- nall/image/core.hpp | 6 +- nall/vector.hpp | 5 + ruby/audio/directsound.cpp | 92 +-- ruby/audio/xaudio2.cpp | 76 +- ruby/input/joypad/directinput.cpp | 54 +- ruby/input/joypad/xinput.cpp | 67 +- ruby/input/keyboard/rawinput.cpp | 24 +- ruby/input/mouse/rawinput.cpp | 48 +- ruby/input/shared/rawinput.cpp | 20 +- ruby/input/windows.cpp | 32 +- ruby/video/direct3d.cpp | 106 ++- ruby/video/directdraw.cpp | 63 +- ruby/video/gdi.cpp | 53 +- ruby/video/wgl.cpp | 74 +- target-tomoko/GNUmakefile | 4 + target-tomoko/presentation/presentation.cpp | 9 +- target-tomoko/presentation/presentation.hpp | 2 + target-tomoko/settings/hotkeys.cpp | 12 +- target-tomoko/settings/input.cpp | 12 +- target-tomoko/tools/cheat-database.cpp | 15 +- target-tomoko/tools/cheat-editor.cpp | 20 +- target-tomoko/tools/state-manager.cpp | 13 +- 188 files changed, 5474 insertions(+), 3834 deletions(-) create mode 100644 data/resource.rc create mode 100644 hiro/core/group.cpp create mode 100644 hiro/core/widget/list-view-cell.cpp create mode 100644 hiro/gtk/group.cpp create mode 100644 hiro/gtk/group.hpp create mode 100644 hiro/gtk/widget/list-view-cell.cpp create mode 100644 hiro/gtk/widget/list-view-cell.hpp create mode 100644 hiro/windows/action/action.hpp delete mode 100644 hiro/windows/action/check-item.cpp delete mode 100644 hiro/windows/action/item.cpp create mode 100644 hiro/windows/action/menu-check-item.cpp create mode 100644 hiro/windows/action/menu-check-item.hpp create mode 100644 hiro/windows/action/menu-item.cpp create mode 100644 hiro/windows/action/menu-item.hpp create mode 100644 hiro/windows/action/menu-radio-item.cpp create mode 100644 hiro/windows/action/menu-radio-item.hpp create mode 100644 hiro/windows/action/menu-separator.cpp create mode 100644 hiro/windows/action/menu-separator.hpp create mode 100644 hiro/windows/action/menu.hpp delete mode 100644 hiro/windows/action/radio-item.cpp delete mode 100644 hiro/windows/action/separator.cpp create mode 100644 hiro/windows/application.hpp create mode 100644 hiro/windows/browser-window.hpp create mode 100644 hiro/windows/desktop.hpp create mode 100644 hiro/windows/font.hpp create mode 100644 hiro/windows/group.cpp create mode 100644 hiro/windows/group.hpp rename hiro/windows/{phoenix.Manifest => hiro.Manifest} (87%) create mode 100644 hiro/windows/hiro.rc create mode 100644 hiro/windows/hotkey.cpp create mode 100644 hiro/windows/hotkey.hpp create mode 100644 hiro/windows/keyboard.hpp create mode 100644 hiro/windows/layout.cpp create mode 100644 hiro/windows/layout.hpp create mode 100644 hiro/windows/menu-bar.cpp create mode 100644 hiro/windows/menu-bar.hpp create mode 100644 hiro/windows/message-window.hpp create mode 100644 hiro/windows/monitor.hpp create mode 100644 hiro/windows/mouse.hpp create mode 100644 hiro/windows/object.hpp delete mode 100644 hiro/windows/phoenix.rc create mode 100644 hiro/windows/popup-menu.cpp create mode 100644 hiro/windows/popup-menu.hpp delete mode 100644 hiro/windows/settings.cpp create mode 100644 hiro/windows/sizable.cpp create mode 100644 hiro/windows/sizable.hpp create mode 100644 hiro/windows/status-bar.cpp create mode 100644 hiro/windows/status-bar.hpp create mode 100644 hiro/windows/timer.hpp create mode 100644 hiro/windows/widget/button.hpp create mode 100644 hiro/windows/widget/canvas.hpp create mode 100644 hiro/windows/widget/check-button.hpp create mode 100644 hiro/windows/widget/check-label.hpp create mode 100644 hiro/windows/widget/combo-button-item.cpp create mode 100644 hiro/windows/widget/combo-button-item.hpp create mode 100644 hiro/windows/widget/combo-button.hpp delete mode 100644 hiro/windows/widget/console.cpp create mode 100644 hiro/windows/widget/frame.hpp create mode 100644 hiro/windows/widget/hex-edit.hpp create mode 100644 hiro/windows/widget/horizontal-scroller.hpp create mode 100644 hiro/windows/widget/horizontal-slider.hpp create mode 100644 hiro/windows/widget/label.hpp create mode 100644 hiro/windows/widget/line-edit.hpp create mode 100644 hiro/windows/widget/list-view-cell.cpp create mode 100644 hiro/windows/widget/list-view-cell.hpp create mode 100644 hiro/windows/widget/list-view-column.cpp create mode 100644 hiro/windows/widget/list-view-column.hpp create mode 100644 hiro/windows/widget/list-view-item.cpp create mode 100644 hiro/windows/widget/list-view-item.hpp create mode 100644 hiro/windows/widget/list-view.hpp create mode 100644 hiro/windows/widget/progress-bar.hpp create mode 100644 hiro/windows/widget/radio-button.hpp create mode 100644 hiro/windows/widget/radio-label.hpp create mode 100644 hiro/windows/widget/tab-frame-item.cpp create mode 100644 hiro/windows/widget/tab-frame-item.hpp create mode 100644 hiro/windows/widget/tab-frame.hpp create mode 100644 hiro/windows/widget/text-edit.hpp create mode 100644 hiro/windows/widget/vertical-scroller.hpp create mode 100644 hiro/windows/widget/vertical-slider.hpp create mode 100644 hiro/windows/widget/viewport.hpp create mode 100644 hiro/windows/widget/widget.hpp create mode 100644 hiro/windows/window.hpp diff --git a/GNUmakefile b/GNUmakefile index d5c0aa52..232b7fcf 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -12,8 +12,7 @@ target := tomoko # console := true # compiler -flags += -I. -O3 -fopenmp -link += -fopenmp +flags += -I. -O3 objects := libco # profile-guided optimization mode @@ -44,11 +43,13 @@ ifeq ($(platform),windows) else ifeq ($(platform),macosx) flags += -march=native else ifeq ($(platform),linux) - flags += -march=native + flags += -march=native -fopenmp + link += -fopenmp link += -Wl,-export-dynamic link += -lX11 -lXext -ldl else ifeq ($(platform),bsd) - flags += -march=native + flags += -march=native -fopenmp + link += -fopenmp link += -Wl,-export-dynamic link += -lX11 -lXext else diff --git a/data/resource.rc b/data/resource.rc new file mode 100644 index 00000000..1302ecd8 --- /dev/null +++ b/data/resource.rc @@ -0,0 +1,2 @@ +1 24 "higan.Manifest" +2 ICON DISCARDABLE "higan.ico" diff --git a/emulator/emulator.hpp b/emulator/emulator.hpp index d51360eb..2a2237b8 100644 --- a/emulator/emulator.hpp +++ b/emulator/emulator.hpp @@ -3,7 +3,7 @@ namespace Emulator { static const char Name[] = "higan"; - static const char Version[] = "094.23"; + static const char Version[] = "094.24"; static const char Author[] = "byuu"; static const char License[] = "GPLv3"; static const char Website[] = "http://byuu.org/"; diff --git a/hiro/components.hpp b/hiro/components.hpp index 4f15c104..6c03415d 100644 --- a/hiro/components.hpp +++ b/hiro/components.hpp @@ -25,6 +25,8 @@ #define Hiro_MessageWindow #define Hiro_Object +#define Hiro_Group + #define Hiro_Hotkey #define Hiro_Timer @@ -48,22 +50,22 @@ #define Hiro_CheckButton #define Hiro_CheckLabel #define Hiro_ComboButton -#define Hiro_Console +//#define Hiro_Console #define Hiro_Frame #define Hiro_HexEdit #define Hiro_HorizontalScroller #define Hiro_HorizontalSlider -#define Hiro_IconView +//#define Hiro_IconView #define Hiro_Label #define Hiro_LineEdit #define Hiro_ListView #define Hiro_ProgressBar #define Hiro_RadioButton #define Hiro_RadioLabel -#define Hiro_SourceView +//#define Hiro_SourceView #define Hiro_TabFrame #define Hiro_TextEdit -#define Hiro_TreeView +//#define Hiro_TreeView #define Hiro_VerticalScroller #define Hiro_VerticalSlider #define Hiro_Viewport diff --git a/hiro/core/action/menu-radio-item.cpp b/hiro/core/action/menu-radio-item.cpp index e09eebad..ec1e3e28 100644 --- a/hiro/core/action/menu-radio-item.cpp +++ b/hiro/core/action/menu-radio-item.cpp @@ -6,20 +6,6 @@ auto mMenuRadioItem::allocate() -> pObject* { // -auto mMenuRadioItem::group(const vector& group) -> void { - for(auto& weak : group) { - if(auto item = weak.acquire()) item->state.group = group; - } - for(auto& weak : group) { - if(auto item = weak.acquire()) { - if(item->self()) item->self()->setGroup(group); - } - } - if(group.size()) { - if(auto item = group.first().acquire()) item->setChecked(); - } -} - auto mMenuRadioItem::checked() const -> bool { return state.checked; } @@ -28,20 +14,36 @@ auto mMenuRadioItem::doActivate() const -> void { if(state.onActivate) return state.onActivate(); } +auto mMenuRadioItem::group() const -> sGroup { + return state.group; +} + auto mMenuRadioItem::onActivate(const function& function) -> type& { state.onActivate = function; return *this; } auto mMenuRadioItem::setChecked() -> type& { - for(auto& weak : state.group) { - if(auto item = weak.acquire()) item->state.checked = false; + if(auto group = this->group()) { + for(auto& weak : group->state.objects) { + if(auto object = weak.acquire()) { + if(auto menuRadioItem = dynamic_cast(object.data())) { + menuRadioItem->state.checked = false; + } + } + } } state.checked = true; signal(setChecked); return *this; } +auto mMenuRadioItem::setGroup(sGroup group) -> type& { + state.group = group; + signal(setGroup, group); + return *this; +} + auto mMenuRadioItem::setText(const string& text) -> type& { state.text = text; signal(setText, text); diff --git a/hiro/core/core.cpp b/hiro/core/core.cpp index 9344fb4b..5eb9a790 100644 --- a/hiro/core/core.cpp +++ b/hiro/core/core.cpp @@ -43,7 +43,10 @@ namespace hiro { #include "mouse.cpp" #include "browser-window.cpp" #include "message-window.cpp" + #include "object.cpp" + #include "group.cpp" + #include "hotkey.cpp" #include "timer.cpp" #include "window.cpp" @@ -80,6 +83,7 @@ namespace hiro { #include "widget/list-view.cpp" #include "widget/list-view-column.cpp" #include "widget/list-view-item.cpp" + #include "widget/list-view-cell.cpp" #include "widget/progress-bar.cpp" #include "widget/radio-button.cpp" #include "widget/radio-label.cpp" diff --git a/hiro/core/core.hpp b/hiro/core/core.hpp index 8985af74..4ba043db 100644 --- a/hiro/core/core.hpp +++ b/hiro/core/core.hpp @@ -31,6 +31,7 @@ namespace hiro { Declare(Keyboard) Declare(Object) +Declare(Group) Declare(Timer) Declare(Hotkey) Declare(Window) @@ -64,6 +65,7 @@ Declare(LineEdit) Declare(ListView) Declare(ListViewColumn) Declare(ListViewItem) +Declare(ListViewCell) Declare(ProgressBar) Declare(RadioButton) Declare(RadioLabel) @@ -409,6 +411,7 @@ struct mObject { auto enabled(bool recursive = false) const -> bool; virtual auto focused() const -> bool; auto font(bool recursive = false) const -> string; + virtual auto group() const -> sGroup; auto offset() const -> signed; auto offset(signed displacement) -> type&; auto parent() const -> mObject*; @@ -417,6 +420,7 @@ struct mObject { auto parentIconView(bool recursive = false) const -> mIconView*; auto parentLayout(bool recursive = false) const -> mLayout*; auto parentListView(bool recursive = false) const -> mListView*; + auto parentListViewItem(bool recursive = false) const -> mListViewItem*; auto parentMenu(bool recursive = false) const -> mMenu*; auto parentMenuBar(bool recursive = false) const -> mMenuBar*; auto parentPopupMenu(bool recursive = false) const -> mPopupMenu*; @@ -432,6 +436,7 @@ struct mObject { virtual auto setEnabled(bool enabled = true) -> type&; virtual auto setFocused() -> type&; virtual auto setFont(const string& font = "") -> type&; + virtual auto setGroup(sGroup group = {}) -> type&; virtual auto setParent(mObject* parent = nullptr, signed offset = -1) -> type&; virtual auto setVisible(bool visible = true) -> type&; auto visible(bool recursive = false) const -> bool; @@ -453,6 +458,23 @@ struct mObject { }; #endif +#if defined(Hiro_Group) +struct mGroup : mObject { + Declare(Group) + using mObject::remove; + + auto append(sObject object) -> type&; + auto object(unsigned offset) const -> sObject; + auto objects() const -> unsigned; + auto remove(sObject object) -> type&; + +//private: + struct State { + vector objects; + } state; +}; +#endif + #if defined(Hiro_Hotkey) struct mHotkey : mObject { Declare(Hotkey) @@ -596,6 +618,7 @@ struct mMenuBar : mObject { auto remove() -> type& override; auto remove(sMenu menu) -> type&; auto reset() -> type&; +//TODO setParent //private: struct State { @@ -616,6 +639,7 @@ struct mPopupMenu : mObject { auto append(sAction action) -> type&; auto remove(sAction action) -> type&; auto reset() -> type&; +//TODO setParent auto setVisible(bool visible = true) -> type& override; //private: @@ -651,6 +675,7 @@ struct mMenu : mAction { auto remove(sAction action) -> type&; auto reset() -> type&; auto setIcon(const image& icon = {}) -> type&; +//TODO setParent auto setText(const string& text = "") -> type&; auto text() const -> string; @@ -721,17 +746,17 @@ struct mMenuRadioItem : mAction { auto checked() const -> bool; auto doActivate() const -> void; + auto group() const -> sGroup override; auto onActivate(const function& function = {}) -> type&; auto setChecked() -> type&; + auto setGroup(sGroup group = {}) -> type& override; auto setText(const string& text = "") -> type&; auto text() const -> string; - static auto group(const vector& group) -> void; - //private: struct State { - bool checked = true; - vector group; + bool checked = false; + sGroup group; function onActivate; string text; } state; @@ -923,12 +948,12 @@ struct mComboButton : mWidget { auto remove(sComboButtonItem item) -> type&; auto reset() -> type&; auto selected() const -> sComboButtonItem; + auto setParent(mObject* parent = nullptr, signed offset = -1) -> type& override; //private: struct State { vector items; function onChange; - signed selected = -1; } state; auto destruct() -> void override; @@ -950,6 +975,7 @@ struct mComboButtonItem : mObject { //private: struct State { image icon; + bool selected = false; string text; } state; }; @@ -989,6 +1015,7 @@ struct mFrame : mWidget { auto layout() const -> sLayout; auto remove(sLayout layout) -> type&; auto reset() -> type&; +//TODO setParent() auto setText(const string& text = "") -> type&; auto text() const -> string; @@ -1106,6 +1133,7 @@ struct mIconView : mWidget { auto setForegroundColor(Color color = {}) -> type&; auto setMultiSelect(bool multipleSelections = true) -> type&; auto setOrientation(Orientation orientation = Orientation::Horizontal) -> type&; +//TODO setParent() auto setSelected(const vector& selections) -> type&; //private: @@ -1203,14 +1231,16 @@ struct mListView : mWidget { auto append(sListViewColumn column) -> type&; auto append(sListViewItem item) -> type&; auto backgroundColor() const -> Color; + auto batchable() const -> bool; auto checkable() const -> bool; + auto checkAll() -> type&; auto checked() const -> vector; auto column(unsigned position) const -> sListViewColumn; auto columns() const -> unsigned; auto doActivate() const -> void; auto doChange() const -> void; auto doContext() const -> void; - auto doEdit(sListViewItem item, sListViewColumn column) const -> void; + auto doEdit(sListViewCell cell) const -> void; auto doSort(sListViewColumn column) const -> void; auto doToggle(sListViewItem item) const -> void; auto foregroundColor() const -> Color; @@ -1218,45 +1248,49 @@ struct mListView : mWidget { auto headerVisible() const -> bool; auto item(unsigned position) const -> sListViewItem; auto items() const -> unsigned; - auto multiSelect() const -> bool; auto onActivate(const function& function = {}) -> type&; auto onChange(const function& function = {}) -> type&; auto onContext(const function& function = {}) -> type&; - auto onEdit(const function& function = {}) -> type&; + auto onEdit(const function& function = {}) -> type&; auto onSort(const function& function = {}) -> type&; auto onToggle(const function& function = {}) -> type&; auto remove(sListViewColumn column) -> type&; auto remove(sListViewItem item) -> type&; auto reset() -> type&; auto resizeColumns() -> type&; + auto selectAll() -> type&; auto selected() const -> sListViewItem; auto selectedItems() const -> vector; auto setBackgroundColor(Color color = {}) -> type&; + auto setBatchable(bool batchable = true) -> type&; auto setCheckable(bool checkable = true) -> type&; - auto setChecked(bool checked = true) -> type&; auto setForegroundColor(Color color = {}) -> type&; auto setGridVisible(bool visible = true) -> type&; auto setHeaderVisible(bool visible = true) -> type&; - auto setMultiSelect(bool multiSelect = true) -> type&; - auto setSelected(bool selected = true) -> type&; + auto setParent(mObject* parent = nullptr, signed offset = -1) -> type& override; + auto setSortable(bool sortable = true) -> type&; + auto sortable() const -> bool; + auto uncheckAll() -> type&; + auto unselectAll() -> type&; //private: struct State { unsigned activeColumn = 0; Color backgroundColor; + bool batchable = false; bool checkable = false; vector columns; Color foregroundColor; bool gridVisible = false; bool headerVisible = false; vector items; - bool multiSelect = false; function onActivate; function onChange; function onContext; - function onEdit; + function onEdit; function onSort; function onToggle; + bool sortable = false; } state; auto destruct() -> void override; @@ -1270,6 +1304,7 @@ struct mListViewColumn : mObject { auto active() const -> bool; auto backgroundColor() const -> Color; auto editable() const -> bool; + auto expandable() const -> bool; auto foregroundColor() const -> Color; auto horizontalAlignment() const -> double; auto icon() const -> image; @@ -1278,17 +1313,16 @@ struct mListViewColumn : mObject { auto setActive() -> type&; auto setBackgroundColor(Color color = {}) -> type&; auto setEditable(bool editable = true) -> type&; + auto setExpandable(bool expandable = true) -> type&; auto setFont(const string& font = "") -> type&; auto setForegroundColor(Color color = {}) -> type&; auto setHorizontalAlignment(double alignment = 0.0) -> type&; auto setIcon(const image& icon = {}) -> type&; auto setResizable(bool resizable = true) -> type&; - auto setSortable(bool sortable = true) -> type&; auto setText(const string& text = "") -> type&; auto setVerticalAlignment(double alignment = 0.5) -> type&; auto setVisible(bool visible = true) -> type&; auto setWidth(signed width = 0) -> type&; - auto sortable() const -> bool; auto text() const -> string; auto verticalAlignment() const -> double; auto width() const -> signed; @@ -1297,12 +1331,12 @@ struct mListViewColumn : mObject { struct State { Color backgroundColor; bool editable = false; + bool expandable = false; string font; Color foregroundColor; double horizontalAlignment = 0.0; image icon; bool resizable = true; - bool sortable = false; string text; double verticalAlignment = 0.5; bool visible = true; @@ -1315,24 +1349,52 @@ struct mListViewColumn : mObject { struct mListViewItem : mObject { Declare(ListViewItem) + auto append(sListViewCell cell) -> type&; + auto backgroundColor() const -> Color; + auto cell(unsigned position) const -> sListViewCell; + auto cells() const -> unsigned; auto checked() const -> bool; - auto icon(unsigned column = 0) const -> image; + auto foregroundColor() const -> Color; auto remove() -> type& override; + auto remove(sListViewCell cell) -> type&; auto selected() const -> bool; + auto setBackgroundColor(Color color = {}) -> type&; auto setChecked(bool checked = true) -> type&; auto setFocused() -> type& override; - auto setIcon(unsigned column, const image& icon = {}) -> type&; + auto setForegroundColor(Color color = {}) -> type&; + auto setParent(mObject* parent = nullptr, signed offset = -1) -> type& override; auto setSelected(bool selected = true) -> type&; - auto setText(const lstring& text) -> type&; - auto setText(unsigned column, const string& text = "") -> type&; - auto text(unsigned column = 0) const -> string; //private: struct State { + Color backgroundColor; + vector cells; bool checked = false; - vector icon; + Color foregroundColor; bool selected = false; - lstring text; + } state; +}; +#endif + +#if defined(Hiro_ListView) +struct mListViewCell : mObject { + Declare(ListViewCell) + + auto backgroundColor() const -> Color; + auto foregroundColor() const -> Color; + auto icon() const -> image; + auto setBackgroundColor(Color color = {}) -> type&; + auto setForegroundColor(Color color = {}) -> type&; + auto setIcon(const image& icon = {}) -> type&; + auto setText(const string& text = "") -> type&; + auto text() const -> string; + +//private: + struct State { + Color backgroundColor; + Color foregroundColor; + image icon; + string text; } state; }; #endif @@ -1358,23 +1420,23 @@ struct mRadioButton : mWidget { auto bordered() const -> bool; auto checked() const -> bool; auto doActivate() const -> void; + auto group() const -> sGroup override; auto icon() const -> image; auto onActivate(const function& function = {}) -> type&; auto orientation() const -> Orientation; auto setBordered(bool bordered = true) -> type&; auto setChecked() -> type&; + auto setGroup(sGroup group = {}) -> type& override; auto setIcon(const image& icon = {}) -> type&; auto setOrientation(Orientation orientation = Orientation::Horizontal) -> type&; auto setText(const string& text = "") -> type&; auto text() const -> string; - static auto group(const vector& group) -> void; - //private: struct State { bool bordered = true; - bool checked = true; - vector group; + bool checked = false; + sGroup group; image icon; function onActivate; Orientation orientation = Orientation::Horizontal; @@ -1389,17 +1451,17 @@ struct mRadioLabel : mWidget { auto checked() const -> bool; auto doActivate() const -> void; + auto group() const -> sGroup override; auto onActivate(const function& function = {}) -> type&; auto setChecked() -> type&; + auto setGroup(sGroup group = {}) -> type& override; auto setText(const string& text = "") -> type&; auto text() const -> string; - static auto group(const vector& group) -> void; - //private: struct State { - bool checked = true; - vector group; + bool checked = false; + sGroup group; function onActivate; string text; } state; @@ -1460,7 +1522,6 @@ struct mTabFrame : mWidget { function onChange; function onClose; function onMove; - unsigned selected = 0; } state; auto destruct() -> void override; @@ -1494,6 +1555,7 @@ struct mTabFrameItem : mObject { image icon; sLayout layout; bool movable = false; + bool selected = false; string text; } state; @@ -1563,6 +1625,7 @@ struct mTreeView : mWidget { auto setBackgroundColor(Color color = {}) -> type&; auto setCheckable(bool checkable = true) -> type&; auto setForegroundColor(Color color = {}) -> type&; +//TODO setParent //private: struct State { @@ -1597,6 +1660,7 @@ struct mTreeViewItem : mObject { auto setChecked(bool checked = true) -> type&; auto setFocused() -> type& override; auto setIcon(const image& icon = {}) -> type&; +//TODO setParent auto setSelected() -> type&; auto setText(const string& text = "") -> type&; auto text() const -> string; diff --git a/hiro/core/group.cpp b/hiro/core/group.cpp new file mode 100644 index 00000000..c13f2b40 --- /dev/null +++ b/hiro/core/group.cpp @@ -0,0 +1,39 @@ +#if defined(Hiro_Group) + +auto mGroup::allocate() -> pObject* { + return new pGroup(*this); +} + +// + +auto mGroup::append(sObject object) -> type& { + if(auto group = instance.acquire()) { + state.objects.append(object); + object->setGroup(group); + } + return *this; +} + +auto mGroup::object(unsigned position) const -> sObject { + if(position < state.objects.size()) return state.objects[position]; + return {}; +} + +auto mGroup::objects() const -> unsigned { + return state.objects.size(); +} + +auto mGroup::remove(sObject object) -> type& { + object->setGroup(); + for(auto offset : range(state.objects)) { + if(auto shared = state.objects[offset].acquire()) { + if(object == shared) { + state.objects.remove(offset); + break; + } + } + } + return *this; +} + +#endif diff --git a/hiro/core/object.cpp b/hiro/core/object.cpp index bfae31f3..1419b1c6 100644 --- a/hiro/core/object.cpp +++ b/hiro/core/object.cpp @@ -31,10 +31,18 @@ auto mObject::destruct() -> void { //if the mObject is not abstract, the pObject delegate is allocated immediately //otherwise, the pObject is not allocated until it is attached to a non-abstract parent auto mObject::abstract() const -> bool { + #if defined(Hiro_Group) + if(dynamic_cast(this)) return false; + #endif + + #if defined(Hiro_Window) if(dynamic_cast(this)) return false; + #endif + #if defined(Hiro_PopupMenu) if(dynamic_cast(this)) return false; #endif + if(auto object = parent()) return object->abstract(); return true; } @@ -56,6 +64,10 @@ auto mObject::font(bool recursive) const -> string { return Application::font(); } +auto mObject::group() const -> sGroup { + return {}; +} + auto mObject::offset() const -> signed { return state.offset; } @@ -119,6 +131,16 @@ auto mObject::parentListView(bool recursive) const -> mListView* { } #endif +#if defined(Hiro_ListView) +auto mObject::parentListViewItem(bool recursive) const -> mListViewItem* { + if(auto listViewItem = dynamic_cast(parent())) return listViewItem; + if(recursive) { + if(auto object = parent()) return object->parentListViewItem(true); + } + return nullptr; +} +#endif + #if defined(Hiro_Menu) auto mObject::parentMenu(bool recursive) const -> mMenu* { if(auto menu = dynamic_cast(parent())) return menu; @@ -246,6 +268,10 @@ auto mObject::setFont(const string& font) -> type& { return *this; } +auto mObject::setGroup(sGroup group) -> type& { + return *this; +} + auto mObject::setParent(mObject* parent, signed offset) -> type& { destruct(); state.parent = parent; diff --git a/hiro/core/widget/combo-button-item.cpp b/hiro/core/widget/combo-button-item.cpp index fdccca62..ff836d36 100644 --- a/hiro/core/widget/combo-button-item.cpp +++ b/hiro/core/widget/combo-button-item.cpp @@ -16,8 +16,7 @@ auto mComboButtonItem::remove() -> type& { } auto mComboButtonItem::selected() const -> bool { - if(auto comboButton = parentComboButton()) return comboButton->state.selected == offset(); - return false; + return state.selected; } auto mComboButtonItem::setIcon(const image& icon) -> type& { @@ -28,9 +27,10 @@ auto mComboButtonItem::setIcon(const image& icon) -> type& { auto mComboButtonItem::setSelected() -> type& { if(auto parent = parentComboButton()) { - parent->state.selected = offset(); - signal(setSelected); + for(auto& item : parent->state.items) item->state.selected = false; } + state.selected = true; + signal(setSelected); return *this; } diff --git a/hiro/core/widget/combo-button.cpp b/hiro/core/widget/combo-button.cpp index 8cff5e23..e92d5303 100644 --- a/hiro/core/widget/combo-button.cpp +++ b/hiro/core/widget/combo-button.cpp @@ -15,7 +15,6 @@ auto mComboButton::append(sComboButtonItem item) -> type& { state.items.append(item); item->setParent(this, items() - 1); signal(append, item); - if(state.selected < 0) item->setSelected(); return *this; } @@ -49,17 +48,23 @@ auto mComboButton::remove(sComboButtonItem item) -> type& { auto mComboButton::reset() -> type& { signal(reset); - for(auto& item : state.items) { - item->setParent(); - } + for(auto& item : state.items) item->setParent(); state.items.reset(); - state.selected = -1; return *this; } auto mComboButton::selected() const -> sComboButtonItem { - if(state.selected >= 0) return state.items[state.selected]; + for(auto& item : state.items) { + if(item->selected()) return item; + } return {}; } +auto mComboButton::setParent(mObject* parent, signed offset) -> type& { + for(auto& item : state.items) item->destruct(); + mObject::setParent(parent, offset); + for(auto& item : state.items) item->setParent(this, item->offset()); + return *this; +} + #endif diff --git a/hiro/core/widget/list-view-cell.cpp b/hiro/core/widget/list-view-cell.cpp new file mode 100644 index 00000000..52ec080f --- /dev/null +++ b/hiro/core/widget/list-view-cell.cpp @@ -0,0 +1,49 @@ +#if defined(Hiro_ListView) + +auto mListViewCell::allocate() -> pObject* { + return new pListViewCell(*this); +} + +// + +auto mListViewCell::backgroundColor() const -> Color { + return state.backgroundColor; +} + +auto mListViewCell::foregroundColor() const -> Color { + return state.foregroundColor; +} + +auto mListViewCell::icon() const -> image { + return state.icon; +} + +auto mListViewCell::setBackgroundColor(Color color) -> type& { + state.backgroundColor = color; + signal(setBackgroundColor, color); + return *this; +} + +auto mListViewCell::setForegroundColor(Color color) -> type& { + state.foregroundColor = color; + signal(setForegroundColor, color); + return *this; +} + +auto mListViewCell::setIcon(const image& icon) -> type& { + state.icon = icon; + signal(setIcon, icon); + return *this; +} + +auto mListViewCell::setText(const string& text) -> type& { + state.text = text; + signal(setText, text); + return *this; +} + +auto mListViewCell::text() const -> string { + return state.text; +} + +#endif diff --git a/hiro/core/widget/list-view-column.cpp b/hiro/core/widget/list-view-column.cpp index 8b8731c7..50769534 100644 --- a/hiro/core/widget/list-view-column.cpp +++ b/hiro/core/widget/list-view-column.cpp @@ -19,6 +19,10 @@ auto mListViewColumn::editable() const -> bool { return state.editable; } +auto mListViewColumn::expandable() const -> bool { + return state.expandable; +} + auto mListViewColumn::foregroundColor() const -> Color { return state.foregroundColor; } @@ -58,6 +62,12 @@ auto mListViewColumn::setEditable(bool editable) -> type& { return *this; } +auto mListViewColumn::setExpandable(bool expandable) -> type& { + state.expandable = expandable; + signal(setExpandable, expandable); + return *this; +} + auto mListViewColumn::setFont(const string& font) -> type& { state.font = font; signal(setFont, this->font(true)); @@ -89,12 +99,6 @@ auto mListViewColumn::setResizable(bool resizable) -> type& { return *this; } -auto mListViewColumn::setSortable(bool sortable) -> type& { - state.sortable = sortable; - signal(setSortable, sortable); - return *this; -} - auto mListViewColumn::setText(const string& text) -> type& { state.text = text; signal(setText, text); @@ -115,15 +119,11 @@ auto mListViewColumn::setVisible(bool visible) -> type& { } auto mListViewColumn::setWidth(signed width) -> type& { - state.width = width; + state.width = max(0, width); signal(setWidth, width); return *this; } -auto mListViewColumn::sortable() const -> bool { - return state.sortable; -} - auto mListViewColumn::text() const -> string { return state.text; } diff --git a/hiro/core/widget/list-view-item.cpp b/hiro/core/widget/list-view-item.cpp index 22796174..4962ec9b 100644 --- a/hiro/core/widget/list-view-item.cpp +++ b/hiro/core/widget/list-view-item.cpp @@ -6,12 +6,32 @@ auto mListViewItem::allocate() -> pObject* { // +auto mListViewItem::append(sListViewCell cell) -> type& { + state.cells.append(cell); + cell->setParent(this, cells() - 1); + signal(append, cell); + return *this; +} + +auto mListViewItem::backgroundColor() const -> Color { + return state.backgroundColor; +} + +auto mListViewItem::cell(unsigned position) const -> sListViewCell { + if(position < cells()) return state.cells[position]; + return {}; +} + +auto mListViewItem::cells() const -> unsigned { + return state.cells.size(); +} + auto mListViewItem::checked() const -> bool { return state.checked; } -auto mListViewItem::icon(unsigned column) const -> image { - return state.icon(column, {}); +auto mListViewItem::foregroundColor() const -> Color { + return state.foregroundColor; } auto mListViewItem::remove() -> type& { @@ -19,10 +39,26 @@ auto mListViewItem::remove() -> type& { return *this; } +auto mListViewItem::remove(sListViewCell cell) -> type& { + signal(remove, cell); + state.cells.remove(cell->offset()); + for(auto n : range(cell->offset(), cells())) { + state.cells[n]->offset(-1); + } + cell->setParent(); + return *this; +} + auto mListViewItem::selected() const -> bool { return state.selected; } +auto mListViewItem::setBackgroundColor(Color color) -> type& { + state.backgroundColor = color; + signal(setBackgroundColor, color); + return *this; +} + auto mListViewItem::setChecked(bool checked) -> type& { state.checked = checked; signal(setChecked, checked); @@ -34,9 +70,16 @@ auto mListViewItem::setFocused() -> type& { return *this; } -auto mListViewItem::setIcon(unsigned column, const image& icon) -> type& { - state.icon(column) = icon; - signal(setIcon, column, icon); +auto mListViewItem::setForegroundColor(Color color) -> type& { + state.foregroundColor = color; + signal(setForegroundColor, color); + return *this; +} + +auto mListViewItem::setParent(mObject* parent, signed offset) -> type& { + for(auto& cell : state.cells) cell->destruct(); + mObject::setParent(parent, offset); + for(auto& cell : state.cells) cell->setParent(this, cell->offset()); return *this; } @@ -46,24 +89,4 @@ auto mListViewItem::setSelected(bool selected) -> type& { return *this; } -auto mListViewItem::setText(const lstring& text) -> type& { - state.text = text; - if(auto listView = parentListView()) { - for(auto column : range(listView->columns())) { - setText(column, text(column, "")); - } - } - return *this; -} - -auto mListViewItem::setText(unsigned column, const string& text) -> type& { - state.text(column) = text; - signal(setText, column, text); - return *this; -} - -auto mListViewItem::text(unsigned column) const -> string { - return state.text(column, ""); -} - #endif diff --git a/hiro/core/widget/list-view.cpp b/hiro/core/widget/list-view.cpp index 8473c1bd..65e3a890 100644 --- a/hiro/core/widget/list-view.cpp +++ b/hiro/core/widget/list-view.cpp @@ -30,10 +30,20 @@ auto mListView::backgroundColor() const -> Color { return state.backgroundColor; } +auto mListView::batchable() const -> bool { + return state.batchable; +} + auto mListView::checkable() const -> bool { return state.checkable; } +auto mListView::checkAll() -> type& { + for(auto& item : state.items) item->state.checked = true; + signal(checkAll); + return *this; +} + auto mListView::checked() const -> vector { vector items; for(auto& item : state.items) { @@ -63,8 +73,8 @@ auto mListView::doContext() const -> void { if(state.onContext) return state.onContext(); } -auto mListView::doEdit(sListViewItem item, sListViewColumn column) const -> void { - if(state.onEdit) return state.onEdit(item, column); +auto mListView::doEdit(sListViewCell cell) const -> void { + if(state.onEdit) return state.onEdit(cell); } auto mListView::doSort(sListViewColumn column) const -> void { @@ -96,10 +106,6 @@ auto mListView::items() const -> unsigned { return state.items.size(); } -auto mListView::multiSelect() const -> bool { - return state.multiSelect; -} - auto mListView::onActivate(const function& function) -> type& { state.onActivate = function; return *this; @@ -115,7 +121,7 @@ auto mListView::onContext(const function& function) -> type& { return *this; } -auto mListView::onEdit(const function& function) -> type& { +auto mListView::onEdit(const function& function) -> type& { state.onEdit = function; return *this; } @@ -154,10 +160,10 @@ auto mListView::remove(sListViewItem item) -> type& { auto mListView::reset() -> type& { signal(reset); - for(auto& column : state.columns) column->setParent(); - state.columns.reset(); for(auto& item : state.items) item->setParent(); state.items.reset(); + for(auto& column : state.columns) column->setParent(); + state.columns.reset(); return *this; } @@ -166,6 +172,12 @@ auto mListView::resizeColumns() -> type& { return *this; } +auto mListView::selectAll() -> type& { + for(auto& item : state.items) item->state.selected = true; + signal(selectAll); + return *this; +} + auto mListView::selected() const -> sListViewItem { for(auto& item : state.items) { if(item->selected()) return item; @@ -187,15 +199,15 @@ auto mListView::setBackgroundColor(Color color) -> type& { return *this; } -auto mListView::setCheckable(bool checkable) -> type& { - state.checkable = checkable; - signal(setCheckable, checkable); +auto mListView::setBatchable(bool batchable) -> type& { + state.batchable = batchable; + signal(setBatchable, batchable); return *this; } -auto mListView::setChecked(bool checked) -> type& { - for(auto& item : state.items) item->state.checked = checked; - signal(setChecked, checked); +auto mListView::setCheckable(bool checkable) -> type& { + state.checkable = checkable; + signal(setCheckable, checkable); return *this; } @@ -217,15 +229,34 @@ auto mListView::setHeaderVisible(bool visible) -> type& { return *this; } -auto mListView::setMultiSelect(bool multiSelect) -> type& { - state.multiSelect = multiSelect; - signal(setMultiSelect, multiSelect); +auto mListView::setParent(mObject* parent, signed offset) -> type& { + for(auto& item : state.items) item->destruct(); + for(auto& column : state.columns) column->destruct(); + mObject::setParent(parent, offset); + for(auto& column : state.columns) column->setParent(this, column->offset()); + for(auto& item : state.items) item->setParent(this, item->offset()); return *this; } -auto mListView::setSelected(bool selected) -> type& { - for(auto& item : state.items) item->state.selected = selected; - signal(setSelected, selected); +auto mListView::setSortable(bool sortable) -> type& { + state.sortable = sortable; + signal(setSortable, sortable); + return *this; +} + +auto mListView::sortable() const -> bool { + return state.sortable; +} + +auto mListView::uncheckAll() -> type& { + for(auto& item : state.items) item->state.checked = false; + signal(uncheckAll); + return *this; +} + +auto mListView::unselectAll() -> type& { + for(auto& item : state.items) item->state.selected = false; + signal(unselectAll); return *this; } diff --git a/hiro/core/widget/radio-button.cpp b/hiro/core/widget/radio-button.cpp index 95dcf421..a509236f 100644 --- a/hiro/core/widget/radio-button.cpp +++ b/hiro/core/widget/radio-button.cpp @@ -6,20 +6,6 @@ auto mRadioButton::allocate() -> pObject* { // -auto mRadioButton::group(const vector>& group) -> void { - for(auto& weak : group) { - if(auto item = weak.acquire()) item->state.group = group; - } - for(auto& weak : group) { - if(auto item = weak.acquire()) { - if(item->self()) item->self()->setGroup(group); - } - } - if(group.size()) { - if(auto item = group.first().acquire()) item->setChecked(); - } -} - auto mRadioButton::bordered() const -> bool { return state.bordered; } @@ -32,6 +18,10 @@ auto mRadioButton::doActivate() const -> void { if(state.onActivate) return state.onActivate(); } +auto mRadioButton::group() const -> sGroup { + return state.group; +} + auto mRadioButton::icon() const -> image { return state.icon; } @@ -52,14 +42,26 @@ auto mRadioButton::setBordered(bool bordered) -> type& { } auto mRadioButton::setChecked() -> type& { - for(auto& weak : state.group) { - if(auto item = weak.acquire()) item->state.checked = false; + if(auto group = this->group()) { + for(auto& weak : group->state.objects) { + if(auto object = weak.acquire()) { + if(auto radioButton = dynamic_cast(object.data())) { + radioButton->state.checked = false; + } + } + } } state.checked = true; signal(setChecked); return *this; } +auto mRadioButton::setGroup(sGroup group) -> type& { + state.group = group; + signal(setGroup, group); + return *this; +} + auto mRadioButton::setIcon(const image& icon) -> type& { state.icon = icon; signal(setIcon, icon); diff --git a/hiro/core/widget/radio-label.cpp b/hiro/core/widget/radio-label.cpp index 3de2b91a..c670c5b2 100644 --- a/hiro/core/widget/radio-label.cpp +++ b/hiro/core/widget/radio-label.cpp @@ -6,20 +6,6 @@ auto mRadioLabel::allocate() -> pObject* { // -auto mRadioLabel::group(const vector>& group) -> void { - for(auto& weak : group) { - if(auto item = weak.acquire()) item->state.group = group; - } - for(auto& weak : group) { - if(auto item = weak.acquire()) { - if(item->self()) item->self()->setGroup(group); - } - } - if(group.size()) { - if(auto item = group.first().acquire()) item->setChecked(); - } -} - auto mRadioLabel::checked() const -> bool { return state.checked; } @@ -28,20 +14,36 @@ auto mRadioLabel::doActivate() const -> void { if(state.onActivate) return state.onActivate(); } +auto mRadioLabel::group() const -> sGroup { + return state.group; +} + auto mRadioLabel::onActivate(const function& function) -> type& { state.onActivate = function; return *this; } auto mRadioLabel::setChecked() -> type& { - for(auto& weak : state.group) { - if(auto item = weak.acquire()) item->state.checked = false; + if(auto group = this->group()) { + for(auto& weak : group->state.objects) { + if(auto object = weak.acquire()) { + if(auto radioLabel = dynamic_cast(object.data())) { + radioLabel->state.checked = false; + } + } + } } state.checked = true; signal(setChecked); return *this; } +auto mRadioLabel::setGroup(sGroup group) -> type& { + state.group = group; + signal(setGroup, group); + return *this; +} + auto mRadioLabel::setText(const string& text) -> type& { state.text = text; signal(setText, text); diff --git a/hiro/core/widget/tab-frame-item.cpp b/hiro/core/widget/tab-frame-item.cpp index 2a885cd7..593828b2 100644 --- a/hiro/core/widget/tab-frame-item.cpp +++ b/hiro/core/widget/tab-frame-item.cpp @@ -15,6 +15,7 @@ auto mTabFrameItem::append(sLayout layout) -> type& { if(auto& layout = state.layout) remove(layout); state.layout = layout; layout->setParent(this, 0); + signal(append, layout); return *this; } @@ -40,8 +41,9 @@ auto mTabFrameItem::remove() -> type& { } auto mTabFrameItem::remove(sLayout layout) -> type& { - layout->setParent(); + signal(remove, layout); state.layout.reset(); + layout->setParent(); return *this; } @@ -51,8 +53,7 @@ auto mTabFrameItem::reset() -> type& { } auto mTabFrameItem::selected() const -> bool { - if(auto tabFrame = parentTabFrame()) return offset() == tabFrame->state.selected; - return false; + return state.selected; } auto mTabFrameItem::setClosable(bool closable) -> type& { @@ -81,7 +82,10 @@ auto mTabFrameItem::setParent(mObject* parent, signed offset) -> type& { } auto mTabFrameItem::setSelected() -> type& { - if(auto tabFrame = parentTabFrame()) tabFrame->state.selected = offset(); + if(auto parent = parentTabFrame()) { + for(auto& item : parent->state.items) item->state.selected = false; + } + state.selected = true; signal(setSelected); return *this; } diff --git a/hiro/core/widget/tab-frame.cpp b/hiro/core/widget/tab-frame.cpp index 65b5af12..54ea86db 100644 --- a/hiro/core/widget/tab-frame.cpp +++ b/hiro/core/widget/tab-frame.cpp @@ -75,7 +75,10 @@ auto mTabFrame::reset() -> type& { } auto mTabFrame::selected() const -> sTabFrameItem { - return state.items[state.selected]; + for(auto& item : state.items) { + if(item->selected()) return item; + } + return {}; } auto mTabFrame::setEdge(Edge edge) -> type& { diff --git a/hiro/core/window.cpp b/hiro/core/window.cpp index e020c0fb..c014d363 100644 --- a/hiro/core/window.cpp +++ b/hiro/core/window.cpp @@ -19,6 +19,7 @@ auto mWindow::append(shared_pointer layout) -> type& { layout->setGeometry(geometry().setPosition(0, 0)); layout->setParent(this, 0); layout->setGeometry(geometry().setPosition(0, 0)); + signal(append, layout); return *this; } @@ -129,6 +130,7 @@ auto mWindow::onSize(const function& function) -> type& { } auto mWindow::remove(shared_pointer layout) -> type& { + signal(remove, layout); layout->setParent(); state.layout.reset(); return *this; diff --git a/hiro/extension/browser-dialog.cpp b/hiro/extension/browser-dialog.cpp index c01090dd..e804c62a 100644 --- a/hiro/extension/browser-dialog.cpp +++ b/hiro/extension/browser-dialog.cpp @@ -35,27 +35,27 @@ auto BrowserDialogWindow::accept() -> void { auto selectedItems = view.selectedItems(); if(state.action == "openFile" && selectedItems) { - string name = selectedItems.first()->text(0); + string name = selectedItems.first()->cell(0)->text(); if(isFolder(name)) return setPath({state.path, name}); state.response.append(string{state.path, name}); } if(state.action == "openFiles") { for(auto selectedItem : selectedItems) { - string name = selectedItem->text(0); + string name = selectedItem->cell(0)->text(); state.response.append(string{state.path, name, isFolder(name) ? "/" : ""}); } } if(state.action == "openFolder" && selectedItems) { - string name = selectedItems.first()->text(0); + string name = selectedItems.first()->cell(0)->text(); if(!isMatch(name)) return setPath({state.path, name}); state.response.append(string{state.path, name, "/"}); } if(state.action == "saveFile") { string name = fileName.text(); - if(!name && selectedItems) name = selectedItems.first()->text(0); + if(!name && selectedItems) name = selectedItems.first()->cell(0)->text(); if(!name || isFolder(name)) return; if(file::exists({state.path, name})) { if(MessageDialog("File already exists; overwrite it?").question() != "Yes") return; @@ -64,7 +64,7 @@ auto BrowserDialogWindow::accept() -> void { } if(state.action == "selectFolder" && selectedItems) { - string name = selectedItems.first()->text(0); + string name = selectedItems.first()->cell(0)->text(); if(isFolder(name)) state.response.append(string{state.path, name, "/"}); } @@ -76,13 +76,13 @@ auto BrowserDialogWindow::activate() -> void { auto selectedItem = view.selected(); if(state.action == "saveFile" && selectedItem) { - string name = selectedItem->text(0); + string name = selectedItem->cell(0)->text(); if(isFolder(name)) return setPath({state.path, name}); fileName.setText(name); } if(state.action == "selectFolder" && selectedItem) { - string name = selectedItem->text(0); + string name = selectedItem->cell(0)->text(); if(isFolder(name)) return setPath({state.path, name}); } @@ -94,7 +94,7 @@ auto BrowserDialogWindow::change() -> void { fileName.setText(""); if(state.action == "saveFile") { if(auto selectedItem = view.selected()) { - string name = selectedItem->text(0); + string name = selectedItem->cell(0)->text(); if(!isFolder(name)) fileName.setText(name); } } @@ -121,7 +121,7 @@ auto BrowserDialogWindow::run() -> lstring { pathHome.setBordered(false).setIcon(Icon::Go::Home).onActivate([&] { setPath(userpath()); }); pathRefresh.setBordered(false).setIcon(Icon::Action::Refresh).onActivate([&] { setPath(state.path); }); pathUp.setBordered(false).setIcon(Icon::Go::Up).onActivate([&] { setPath(state.path.dirname()); }); - view.setMultiSelect(state.action == "openFiles").onActivate([&] { activate(); }).onChange([&] { change(); }); + view.setBatchable(state.action == "openFiles").onActivate([&] { activate(); }).onChange([&] { change(); }); filterList.setVisible(state.action != "selectFolder").onChange([&] { setPath(state.path); }); for(auto& filter : state.filters) { auto part = filter.split<1>("|"); @@ -161,8 +161,8 @@ auto BrowserDialogWindow::setPath(string path) -> void { pathName.setText(state.path = path); view.reset(); - view.append(ListViewColumn().setWidth(~0)); - view.append(ListViewColumn().setWidth( 0).setForegroundColor({192, 128, 128})); + view.append(ListViewColumn().setExpandable()); + view.append(ListViewColumn().setForegroundColor({192, 128, 128})); auto contents = directory::contents(path); bool folderMode = state.action == "openFolder"; @@ -171,20 +171,20 @@ auto BrowserDialogWindow::setPath(string path) -> void { if(!content.endsWith("/")) continue; if(folderMode && isMatch(content.rtrim("/"))) continue; - ListViewItem item{&view}; - item.setIcon(0, Icon::Emblem::Folder); - item.setText(0, content.rtrim("/")); - item.setText(1, octal<3>(storage::mode({path, content}) & 0777)); + view.append(ListViewItem() + .append(ListViewCell().setText(content.rtrim("/")).setIcon(Icon::Emblem::Folder)) + .append(ListViewCell().setText(octal<3>(storage::mode({path, content}) & 0777))) + ); } for(auto content : contents) { if(content.endsWith("/") && !folderMode) continue; if(!isMatch(content.rtrim("/"))) continue; - ListViewItem item{&view}; - item.setIcon(0, folderMode ? Icon::Action::Open : Icon::Emblem::File); - item.setText(0, content.rtrim("/")); - item.setText(1, octal<3>(storage::mode({path, content}) & 0777)); + view.append(ListViewItem() + .append(ListViewCell().setText(content.rtrim("/")).setIcon(folderMode ? Icon::Action::Open : Icon::Emblem::File)) + .append(ListViewCell().setText(octal<3>(storage::mode({path, content}) & 0777))) + ); } if(view.items()) view.item(0)->setSelected(); diff --git a/hiro/extension/shared.hpp b/hiro/extension/shared.hpp index ef817996..57fe6a90 100644 --- a/hiro/extension/shared.hpp +++ b/hiro/extension/shared.hpp @@ -1,6 +1,6 @@ #define Declare(Name) \ using type = Name; \ - Name() : s##Name(new m##Name, [](m##Name* p) { \ + Name() : s##Name(new m##Name, [](auto p) { \ p->unbind(); \ delete p; \ }) { \ @@ -51,6 +51,27 @@ struct Object : sObject { }; #endif +#if defined(Hiro_Group) +struct Group : sGroup { + using type = Group; + Group() : sGroup(new mGroup, [](auto p) { p->unbind(); delete p; }) { (*this)->bind(*this); } + template Group(P&&... p) : Group() { _append(std::forward

(p)...); } + auto self() const -> mGroup& { return (mGroup&)operator*(); } + + auto append(sObject object) -> type& { return self().append(object), *this; } + auto object(unsigned position) const -> sObject { return self().object(position); } + auto objects() const -> unsigned { return self().objects(); } + auto remove(sObject object) -> type& { return self().remove(object), *this; } + +private: + auto _append() -> void {} + template auto _append(T* object, P&&... p) -> void { + append(*object); + _append(std::forward

(p)...); + } +}; +#endif + #if defined(Hiro_Hotkey) struct Hotkey : sHotkey { DeclareObject(Hotkey) @@ -222,16 +243,11 @@ struct MenuRadioItem : sMenuRadioItem { auto checked() const -> bool { return self().checked(); } auto doActivate() const -> void { return self().doActivate(); } + auto group() const -> sGroup { return self().group(); } auto onActivate(const function& function = {}) -> type& { return self().onActivate(function), *this; } auto setChecked() -> type& { return self().setChecked(), *this; } auto setText(const string& text = "") -> type& { return self().setText(text), *this; } auto text() const -> string { return self().text(); } - - static auto group(const vector& group) -> void { - vector items; - for(auto& item : group) items.append(item); - return mMenuRadioItem::group(items); - } }; #endif @@ -345,6 +361,7 @@ struct ComboButton : sComboButton { auto remove(sComboButtonItem item) -> type& { return self().remove(item), *this; } auto reset() -> type& { return self().reset(), *this; } auto selected() const -> sComboButtonItem { return self().selected(); } + auto setParent(mObject* parent = nullptr, signed offset = -1) -> type& { return self().setParent(parent, offset), *this; } }; #endif @@ -524,14 +541,16 @@ struct ListView : sListView { auto append(sListViewColumn column) -> type& { return self().append(column), *this; } auto append(sListViewItem item) -> type& { return self().append(item), *this; } auto backgroundColor() const -> Color { return self().backgroundColor(); } + auto batchable() const -> bool { return self().batchable(); } auto checkable() const -> bool { return self().checkable(); } + auto checkAll() -> type& { return self().checkAll(), *this; } auto checked() const -> vector { return self().checked(); } auto column(unsigned position) -> sListViewColumn { return self().column(position); } auto columns() const -> unsigned { return self().columns(); } auto doActivate() const -> void { return self().doActivate(); } auto doChange() const -> void { return self().doChange(); } auto doContext() const -> void { return self().doContext(); } - auto doEdit(sListViewItem item, sListViewColumn column) const -> void { return self().doEdit(item, column); } + auto doEdit(sListViewCell cell) const -> void { return self().doEdit(cell); } auto doSort(sListViewColumn column) const -> void { return self().doSort(column); } auto doToggle(sListViewItem item) const -> void { return self().doToggle(item); } auto foregroundColor() const -> Color { return self().foregroundColor(); } @@ -539,27 +558,29 @@ struct ListView : sListView { auto headerVisible() const -> bool { return self().headerVisible(); } auto item(unsigned position) -> sListViewItem { return self().item(position); } auto items() const -> unsigned { return self().items(); } - auto multiSelect() const -> bool { return self().multiSelect(); } auto onActivate(const function& function = {}) -> type& { return self().onActivate(function), *this; } auto onChange(const function& function = {}) -> type& { return self().onChange(function), *this; } auto onContext(const function& function = {}) -> type& { return self().onContext(function), *this; } - auto onEdit(const function& function = {}) -> type& { return self().onEdit(function), *this; } + auto onEdit(const function& function = {}) -> type& { return self().onEdit(function), *this; } auto onSort(const function& function = {}) -> type& { return self().onSort(function), *this; } auto onToggle(const function& function = {}) -> type& { return self().onToggle(function), *this; } auto remove(sListViewColumn column) -> type& { return self().remove(column), *this; } auto remove(sListViewItem item) -> type& { return self().remove(item), *this; } auto reset() -> type& { return self().reset(), *this; } auto resizeColumns() -> type& { return self().resizeColumns(), *this; } + auto selectAll() -> type& { return self().selectAll(), *this; } auto selected() const -> sListViewItem { return self().selected(); } auto selectedItems() const -> vector { return self().selectedItems(); } auto setBackgroundColor(Color color = {}) -> type& { return self().setBackgroundColor(color), *this; } + auto setBatchable(bool batchable = true) -> type& { return self().setBatchable(batchable), *this; } auto setCheckable(bool checkable = true) -> type& { return self().setCheckable(checkable), *this; } - auto setChecked(bool checked = true) -> type& { return self().setChecked(checked), *this; } auto setForegroundColor(Color color = {}) -> type& { return self().setForegroundColor(color), *this; } auto setGridVisible(bool visible = true) -> type& { return self().setGridVisible(visible), *this; } auto setHeaderVisible(bool visible = true) -> type& { return self().setHeaderVisible(visible), *this; } - auto setMultiSelect(bool multiSelect = true) -> type& { return self().setMultiSelect(multiSelect), *this; } - auto setSelected(bool selected = true) -> type& { return self().setSelected(selected), *this; } + auto setSortable(bool sortable = true) -> type& { return self().setSortable(sortable), *this; } + auto sortable() const -> bool { return self().sortable(); } + auto uncheckAll() -> type& { return self().uncheckAll(), *this; } + auto unselectAll() -> type& { return self().unselectAll(), *this; } }; #endif @@ -570,6 +591,7 @@ struct ListViewColumn : sListViewColumn { auto active() const -> bool { return self().active(); } auto backgroundColor() const -> Color { return self().backgroundColor(); } auto editable() const -> bool { return self().editable(); } + auto expandable() const -> bool { return self().expandable(); } auto foregroundColor() const -> Color { return self().foregroundColor(); } auto horizontalAlignment() const -> double { return self().horizontalAlignment(); } auto icon() const -> image { return self().icon(); } @@ -577,15 +599,14 @@ struct ListViewColumn : sListViewColumn { auto setActive() -> type& { return self().setActive(), *this; } auto setBackgroundColor(Color color = {}) -> type& { return self().setBackgroundColor(color), *this; } auto setEditable(bool editable = true) -> type& { return self().setEditable(editable), *this; } + auto setExpandable(bool expandable = true) -> type& { return self().setExpandable(expandable), *this; } auto setForegroundColor(Color color = {}) -> type& { return self().setForegroundColor(color), *this; } auto setHorizontalAlignment(double alignment = 0.0) -> type& { return self().setHorizontalAlignment(alignment), *this; } auto setIcon(const image& icon = {}) -> type& { return self().setIcon(icon), *this; } auto setResizable(bool resizable = true) -> type& { return self().setResizable(resizable), *this; } - auto setSortable(bool sortable = true) -> type& { return self().setSortable(sortable), *this; } auto setText(const string& text = "") -> type& { return self().setText(text), *this; } auto setVerticalAlignment(double alignment = 0.5) -> type& { return self().setVerticalAlignment(alignment), *this; } auto setWidth(signed width = 0) -> type& { return self().setWidth(width), *this; } - auto sortable() const -> bool { return self().sortable(); } auto text() const -> string { return self().text(); } auto verticalAlignment() const -> double { return self().verticalAlignment(); } auto width() const -> signed { return self().width(); } @@ -596,15 +617,33 @@ struct ListViewColumn : sListViewColumn { struct ListViewItem : sListViewItem { DeclareObject(ListViewItem) + auto append(sListViewCell cell) -> type& { return self().append(cell), *this; } + auto backgroundColor() const -> Color { return self().backgroundColor(); } + auto cell(unsigned position) const -> sListViewCell { return self().cell(position); } + auto cells() const -> unsigned { return self().cells(); } auto checked() const -> bool { return self().checked(); } - auto icon(unsigned column = 0) const -> image { return self().icon(column); } + auto foregroundColor() const -> Color { return self().foregroundColor(); } + auto remove(sListViewCell cell) -> type& { return self().remove(cell), *this; } auto selected() const -> bool { return self().selected(); } + auto setBackgroundColor(Color color = {}) -> type& { return self().setBackgroundColor(color), *this; } auto setChecked(bool checked = true) -> type& { return self().setChecked(checked), *this; } - auto setIcon(unsigned column, const image& icon = {}) -> type& { return self().setIcon(column, icon), *this; } + auto setForegroundColor(Color color = {}) -> type& { return self().setForegroundColor(color), *this; } auto setSelected(bool selected = true) -> type& { return self().setSelected(selected), *this; } - auto setText(const lstring& text) -> type& { return self().setText(text), *this; } - auto setText(unsigned column, const string& text = "") -> type& { return self().setText(column, text), *this; } - auto text(unsigned column = 0) const -> string { return self().text(column); } +}; +#endif + +#if defined(Hiro_ListView) +struct ListViewCell : sListViewCell { + DeclareObject(ListViewCell) + + auto backgroundColor() const -> Color { return self().backgroundColor(); } + auto foregroundColor() const -> Color { return self().foregroundColor(); } + auto icon() const -> image { return self().icon(); } + auto setBackgroundColor(Color color = {}) -> type& { return self().setBackgroundColor(color), *this; } + auto setForegroundColor(Color color = {}) -> type& { return self().setForegroundColor(color), *this; } + auto setIcon(const image& icon = {}) -> type& { return self().setIcon(icon), *this; } + auto setText(const string& text = "") -> type& { return self().setText(text), *this; } + auto text() const -> string { return self().text(); } }; #endif @@ -624,6 +663,7 @@ struct RadioButton : sRadioButton { auto bordered() const -> bool { return self().bordered(); } auto checked() const -> bool { return self().checked(); } auto doActivate() const -> void { return self().doActivate(); } + auto group() const -> sGroup { return self().group(); } auto icon() const -> image { return self().icon(); } auto onActivate(const function& function = {}) -> type& { return self().onActivate(function), *this; } auto orientation() const -> Orientation { return self().orientation(); } @@ -633,12 +673,6 @@ struct RadioButton : sRadioButton { auto setOrientation(Orientation orientation = Orientation::Horizontal) -> type& { return self().setOrientation(orientation), *this; } auto setText(const string& text = "") -> type& { return self().setText(text), *this; } auto text() const -> string { return self().text(); } - - static auto group(const vector& group) -> void { - vector items; - for(auto& item : group) items.append(item); - return mRadioButton::group(items); - } }; #endif @@ -648,16 +682,11 @@ struct RadioLabel : sRadioLabel { auto checked() const -> bool { return self().checked(); } auto doActivate() const -> void { return self().doActivate(); } + auto group() const -> sGroup { return self().group(); } auto onActivate(const function& function = {}) -> type& { return self().onActivate(function), *this; } auto setChecked() -> type& { return self().setChecked(), *this; } auto setText(const string& text = "") -> type& { return self().setText(text), *this; } auto text() const -> string { return self().text(); } - - static auto group(const vector& group) -> void { - vector items; - for(auto& item : items) items.append(item); - return mRadioLabel::group(items); - } }; #endif diff --git a/hiro/gtk/action/menu-radio-item.cpp b/hiro/gtk/action/menu-radio-item.cpp index 89c0bf97..8dc75941 100644 --- a/hiro/gtk/action/menu-radio-item.cpp +++ b/hiro/gtk/action/menu-radio-item.cpp @@ -8,13 +8,11 @@ static auto MenuRadioItem_activate(GtkCheckMenuItem* gtkCheckMenuItem, pMenuRadi auto pMenuRadioItem::construct() -> void { widget = gtk_radio_menu_item_new_with_mnemonic(0, ""); - setGroup(state().group); + gtkCheckMenuItem = GTK_CHECK_MENU_ITEM(widget); + gtkRadioMenuItem = GTK_RADIO_MENU_ITEM(widget); + setText(state().text); - for(auto& weak : state().group) { - if(auto item = weak.acquire()) { - if(item->self()) gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item->self()->widget), item->checked()); - } - } + g_signal_connect(G_OBJECT(widget), "toggled", G_CALLBACK(MenuRadioItem_activate), (gpointer)this); } @@ -23,55 +21,54 @@ auto pMenuRadioItem::destruct() -> void { } auto pMenuRadioItem::setChecked() -> void { - _parent().lock(); - for(auto& weak : state().group) { - if(auto item = weak.acquire()) { - if(item->self()) gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item->self()->widget), false); - } - } - gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(widget), true); - _parent().unlock(); + lock(); + gtk_check_menu_item_set_active(gtkCheckMenuItem, true); + unlock(); } -auto pMenuRadioItem::setGroup(const vector>& group) -> void { - _parent().lock(); - shared_pointer first; - for(auto& weak : group) { - if(!first) { - first = weak.acquire(); - continue; - } - if(auto item = weak.acquire()) { - if(item->self() && first->self()) { - GSList* currentGroup = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(first->self()->widget)); - if(currentGroup != gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item->self()->widget))) { - gtk_radio_menu_item_set_group(GTK_RADIO_MENU_ITEM(item->self()->widget), currentGroup); +auto pMenuRadioItem::setGroup(sGroup group) -> void { + if(!group) return; + + maybe gtkRadioMenuItem; + for(auto& weak : group->state.objects) { + if(auto object = weak.acquire()) { + if(auto menuRadioItem = dynamic_cast(object.data())) { + if(auto self = menuRadioItem->self()) { + self->lock(); + gtk_radio_menu_item_set_group(self->gtkRadioMenuItem, nullptr); + if(!gtkRadioMenuItem) gtkRadioMenuItem = self->gtkRadioMenuItem; + else gtk_radio_menu_item_set_group(self->gtkRadioMenuItem, gtk_radio_menu_item_get_group(*gtkRadioMenuItem)); + gtk_check_menu_item_set_active(self->gtkCheckMenuItem, menuRadioItem->checked()); + self->unlock(); } } } } - _parent().unlock(); } auto pMenuRadioItem::setText(const string& text) -> void { gtk_menu_item_set_label(GTK_MENU_ITEM(widget), _mnemonic(text)); } -auto pMenuRadioItem::_doActivate() -> void { - if(!_parent().locked()) { - bool wasChecked = state().checked; - self().setChecked(); - if(!wasChecked) self().doActivate(); +auto pMenuRadioItem::groupLocked() const -> bool { + if(auto group = state().group) { + for(auto& weak : group->state.objects) { + if(auto object = weak.acquire()) { + if(auto self = object->self()) { + if(self->locked()) return true; + } + } + } + return false; } + return locked(); } -auto pMenuRadioItem::_parent() -> pMenuRadioItem& { - if(state().group.size()) { - if(auto item = state().group.first().acquire()) { - if(item->self()) return *item->self(); - } - } - return *this; +auto pMenuRadioItem::_doActivate() -> void { + if(groupLocked()) return; + bool wasChecked = state().checked; + self().setChecked(); + if(!wasChecked) self().doActivate(); } } diff --git a/hiro/gtk/action/menu-radio-item.hpp b/hiro/gtk/action/menu-radio-item.hpp index 0eedab59..f943f268 100644 --- a/hiro/gtk/action/menu-radio-item.hpp +++ b/hiro/gtk/action/menu-radio-item.hpp @@ -6,11 +6,15 @@ struct pMenuRadioItem : pAction { Declare(MenuRadioItem, Action) auto setChecked() -> void; - auto setGroup(const vector>& group) -> void; + auto setGroup(sGroup group) -> void; auto setText(const string& text) -> void; + auto groupLocked() const -> bool; + auto _doActivate() -> void; - auto _parent() -> pMenuRadioItem&; + + GtkCheckMenuItem* gtkCheckMenuItem = nullptr; + GtkRadioMenuItem* gtkRadioMenuItem = nullptr; }; } diff --git a/hiro/gtk/font.cpp b/hiro/gtk/font.cpp index b41c2bc0..f5e09190 100644 --- a/hiro/gtk/font.cpp +++ b/hiro/gtk/font.cpp @@ -2,31 +2,31 @@ namespace hiro { -string pFont::serif(unsigned size, string style) { +auto pFont::serif(unsigned size, string style) -> string { if(size == 0) size = 8; if(style == "") style = "Normal"; return {"Serif, ", size, ", ", style}; } -string pFont::sans(unsigned size, string style) { +auto pFont::sans(unsigned size, string style) -> string { if(size == 0) size = 8; if(style == "") style = "Normal"; return {"Sans, ", size, ", ", style}; } -string pFont::monospace(unsigned size, string style) { +auto pFont::monospace(unsigned size, string style) -> string { if(size == 0) size = 8; return {"Liberation Mono, ", size, ", ", style}; } -Size pFont::size(string font, string text) { +auto pFont::size(string font, string text) -> Size { PangoFontDescription* description = create(font); Size size = pFont::size(description, text); free(description); return size; } -PangoFontDescription* pFont::create(string description) { +auto pFont::create(string description) -> PangoFontDescription* { lstring part = description.split<2>(",").strip(); string family = "Sans"; @@ -47,11 +47,11 @@ PangoFontDescription* pFont::create(string description) { return font; } -void pFont::free(PangoFontDescription* font) { +auto pFont::free(PangoFontDescription* font) -> void { pango_font_description_free(font); } -Size pFont::size(PangoFontDescription* font, string text) { +auto pFont::size(PangoFontDescription* font, string text) -> Size { PangoContext* context = gdk_pango_context_get_for_screen(gdk_screen_get_default()); PangoLayout* layout = pango_layout_new(context); pango_layout_set_font_description(layout, font); @@ -62,13 +62,13 @@ Size pFont::size(PangoFontDescription* font, string text) { return {width, height}; } -void pFont::setFont(GtkWidget* widget, string font) { +auto pFont::setFont(GtkWidget* widget, string font) -> void { auto gtkFont = pFont::create(font); pFont::setFont(widget, (gpointer)gtkFont); pFont::free(gtkFont); } -void pFont::setFont(GtkWidget* widget, gpointer font) { +auto pFont::setFont(GtkWidget* widget, gpointer font) -> void { if(font == nullptr) return; gtk_widget_modify_font(widget, (PangoFontDescription*)font); if(GTK_IS_CONTAINER(widget)) { diff --git a/hiro/gtk/font.hpp b/hiro/gtk/font.hpp index 711176f2..71101ddc 100644 --- a/hiro/gtk/font.hpp +++ b/hiro/gtk/font.hpp @@ -3,16 +3,16 @@ namespace hiro { struct pFont { - static string serif(unsigned size, string style); - static string sans(unsigned size, string style); - static string monospace(unsigned size, string style); - static Size size(string font, string text); + static auto serif(unsigned size, string style) -> string; + static auto sans(unsigned size, string style) -> string; + static auto monospace(unsigned size, string style) -> string; + static auto size(string font, string text) -> Size; - static PangoFontDescription* create(string description); - static void free(PangoFontDescription* font); - static Size size(PangoFontDescription* font, string text); - static void setFont(GtkWidget* widget, string font); - static void setFont(GtkWidget* widget, gpointer font); + static auto create(string description) -> PangoFontDescription*; + static auto free(PangoFontDescription* font) -> void; + static auto size(PangoFontDescription* font, string text) -> Size; + static auto setFont(GtkWidget* widget, string font) -> void; + static auto setFont(GtkWidget* widget, gpointer font) -> void; }; } diff --git a/hiro/gtk/group.cpp b/hiro/gtk/group.cpp new file mode 100644 index 00000000..cbf387ff --- /dev/null +++ b/hiro/gtk/group.cpp @@ -0,0 +1,13 @@ +#if defined(Hiro_Group) + +namespace hiro { + +auto pGroup::construct() -> void { +} + +auto pGroup::destruct() -> void { +} + +} + +#endif diff --git a/hiro/gtk/group.hpp b/hiro/gtk/group.hpp new file mode 100644 index 00000000..a2ac6d72 --- /dev/null +++ b/hiro/gtk/group.hpp @@ -0,0 +1,11 @@ +#if defined(Hiro_Group) + +namespace hiro { + +struct pGroup : pObject { + Declare(Group, Object) +}; + +} + +#endif diff --git a/hiro/gtk/keyboard.cpp b/hiro/gtk/keyboard.cpp index 5d20e179..8d0390d2 100644 --- a/hiro/gtk/keyboard.cpp +++ b/hiro/gtk/keyboard.cpp @@ -240,7 +240,7 @@ auto pKeyboard::initialize() -> void { #include #endif - //print("[phoenix/gtk] warning: unhandled key: ", key, "\n"); + //print("[hiro/gtk] warning: unhandled key: ", key, "\n"); append(0); } #undef map diff --git a/hiro/gtk/platform.cpp b/hiro/gtk/platform.cpp index 63868a21..c77ee092 100644 --- a/hiro/gtk/platform.cpp +++ b/hiro/gtk/platform.cpp @@ -8,7 +8,10 @@ #include "mouse.cpp" #include "browser-window.cpp" #include "message-window.cpp" + #include "object.cpp" +#include "group.cpp" + #include "hotkey.cpp" #include "timer.cpp" #include "window.cpp" @@ -45,6 +48,7 @@ #include "widget/list-view.cpp" #include "widget/list-view-column.cpp" #include "widget/list-view-item.cpp" +#include "widget/list-view-cell.cpp" #include "widget/progress-bar.cpp" #include "widget/radio-button.cpp" #include "widget/radio-label.cpp" diff --git a/hiro/gtk/platform.hpp b/hiro/gtk/platform.hpp index 06b3f5ea..354e96cc 100644 --- a/hiro/gtk/platform.hpp +++ b/hiro/gtk/platform.hpp @@ -3,7 +3,7 @@ namespace hiro { struct pMenu; struct pLayout; struct pWidget; -}; +} #define Declare(Name, Base) \ p##Name(m##Name& reference) : p##Base(reference) {} \ @@ -19,7 +19,10 @@ namespace hiro { #include "mouse.hpp" #include "browser-window.hpp" #include "message-window.hpp" + #include "object.hpp" +#include "group.hpp" + #include "hotkey.hpp" #include "timer.hpp" #include "window.hpp" @@ -56,6 +59,7 @@ namespace hiro { #include "widget/list-view.hpp" #include "widget/list-view-column.hpp" #include "widget/list-view-item.hpp" +#include "widget/list-view-cell.hpp" #include "widget/progress-bar.hpp" #include "widget/radio-button.hpp" #include "widget/radio-label.hpp" diff --git a/hiro/gtk/utility.cpp b/hiro/gtk/utility.cpp index 24c76ab7..49453b7f 100644 --- a/hiro/gtk/utility.cpp +++ b/hiro/gtk/utility.cpp @@ -12,6 +12,8 @@ static auto CreateColor(const Color& color) -> GdkColor { #endif static auto CreatePixbuf(const nall::image& image, bool scale = false) -> GdkPixbuf* { + if(!image) return nullptr; + nall::image gdkImage = image; gdkImage.transform(0, 32, 255u << 24, 255u << 0, 255u << 8, 255u << 16); if(scale) gdkImage.scale(15, 15); diff --git a/hiro/gtk/widget/combo-button-item.cpp b/hiro/gtk/widget/combo-button-item.cpp index ff716ac3..3a54544f 100644 --- a/hiro/gtk/widget/combo-button-item.cpp +++ b/hiro/gtk/widget/combo-button-item.cpp @@ -8,17 +8,12 @@ auto pComboButtonItem::construct() -> void { auto pComboButtonItem::destruct() -> void { } -auto pComboButtonItem::setIcon(const image& icon) -> void { +auto pComboButtonItem::setIcon(image icon) -> void { if(auto parent = _parent()) { - if(icon) { - auto copy = icon; - auto size = pFont::size(self().font(true), " ").height(); - copy.scale(size, size); - auto pixbuf = CreatePixbuf(copy); - gtk_list_store_set(parent->gtkListStore, >kIter, 0, pixbuf, -1); - } else { - gtk_list_store_set(parent->gtkListStore, >kIter, 0, nullptr, -1); - } + auto size = pFont::size(self().font(true), " ").height(); + if(icon) icon.scale(size, size); + auto pixbuf = CreatePixbuf(icon); + gtk_list_store_set(parent->gtkListStore, >kIter, 0, pixbuf, -1); } } diff --git a/hiro/gtk/widget/combo-button-item.hpp b/hiro/gtk/widget/combo-button-item.hpp index 3afd541f..0ed29bd9 100644 --- a/hiro/gtk/widget/combo-button-item.hpp +++ b/hiro/gtk/widget/combo-button-item.hpp @@ -5,7 +5,7 @@ namespace hiro { struct pComboButtonItem : pObject { Declare(ComboButtonItem, Object) - auto setIcon(const image& icon) -> void; + auto setIcon(image icon) -> void; auto setSelected() -> void; auto setText(const string& text) -> void; diff --git a/hiro/gtk/widget/combo-button.cpp b/hiro/gtk/widget/combo-button.cpp index 4062a940..08eebddd 100644 --- a/hiro/gtk/widget/combo-button.cpp +++ b/hiro/gtk/widget/combo-button.cpp @@ -17,10 +17,7 @@ auto pComboButton::construct() -> void { gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkWidget), gtkCellText, true); gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkWidget), gtkCellText, "text", 1, nullptr); - for(auto& item : state().items) { - append(item); - if(item->selected()) item->setSelected(); - } + for(auto& item : state().items) append(item); g_signal_connect(G_OBJECT(gtkWidget), "changed", G_CALLBACK(ComboButton_change), (gpointer)this); @@ -33,11 +30,13 @@ auto pComboButton::destruct() -> void { auto pComboButton::append(sComboButtonItem item) -> void { lock(); - if(auto delegate = item->self()) { - gtk_list_store_append(gtkListStore, &delegate->gtkIter); - item->setIcon(item->state.icon); - item->setText(item->state.text); + if(auto self = item->self()) { + gtk_list_store_append(gtkListStore, &self->gtkIter); + self->setIcon(item->state.icon); + if(item->state.selected) self->setSelected(); + self->setText(item->state.text); } + if(gtk_combo_box_get_active(gtkComboBox) < 0) item->setSelected(); unlock(); } @@ -46,25 +45,19 @@ auto pComboButton::minimumSize() const -> Size { signed maximumWidth = 0; for(auto& item : state().items) { maximumWidth = max(maximumWidth, - (item->state.icon ? 2 + pFont::size(font, " ").height() : 0) + (item->state.icon ? pFont::size(font, " ").height() + 2 : 0) + pFont::size(font, item->state.text).width() ); } - Size size = pFont::size(font, " "); - return {maximumWidth + 40, size.height() + 12}; + return {maximumWidth + 40, pFont::size(font, " ").height() + 12}; } auto pComboButton::remove(sComboButtonItem item) -> void { lock(); if(auto delegate = item->self()) { gtk_list_store_remove(gtkListStore, &delegate->gtkIter); - //if the currently selected item is removed; GTK+ deselects everything - //detect this behavior and select the first item instead of nothing - if(gtk_combo_box_get_active(GTK_COMBO_BOX(gtkWidget)) < 0) { - if(gtk_tree_model_iter_n_children(gtkTreeModel, nullptr) > 0) { - gtk_combo_box_set_active(GTK_COMBO_BOX(gtkWidget), 0); - state().selected = 0; - } + if(gtk_combo_box_get_active(gtkComboBox) < 0) { + if(auto item = self().item(0)) item->setSelected(); } } unlock(); @@ -83,10 +76,13 @@ auto pComboButton::setFont(const string& font) -> void { } auto pComboButton::_updateSelected() -> void { + for(auto& item : state().items) item->state.selected = false; signed selected = gtk_combo_box_get_active(gtkComboBox); if(selected >= 0) { - state().selected = selected; - if(!locked()) self().doChange(); + if(auto item = self().item(selected)) { + item->state.selected = true; + if(!locked()) self().doChange(); + } } } diff --git a/hiro/gtk/widget/list-view-cell.cpp b/hiro/gtk/widget/list-view-cell.cpp new file mode 100644 index 00000000..da3fb167 --- /dev/null +++ b/hiro/gtk/widget/list-view-cell.cpp @@ -0,0 +1,40 @@ +#if defined(Hiro_ListView) + +namespace hiro { + +auto pListViewCell::construct() -> void { +} + +auto pListViewCell::destruct() -> void { +} + +auto pListViewCell::setBackgroundColor(Color color) -> void { +} + +auto pListViewCell::setForegroundColor(Color color) -> void { +} + +auto pListViewCell::setIcon(const image& icon) -> void { + if(auto item = _parent()) { + if(auto view = item->_parent()) { + gtk_list_store_set(view->gtkListStore, &item->gtkIter, 1 + self().offset() * 2, CreatePixbuf(icon), -1); + } + } +} + +auto pListViewCell::setText(const string& text) -> void { + if(auto item = _parent()) { + if(auto view = item->_parent()) { + gtk_list_store_set(view->gtkListStore, &item->gtkIter, 1 + self().offset() * 2 + 1, text.data(), -1); + } + } +} + +auto pListViewCell::_parent() -> pListViewItem* { + if(auto parent = self().parentListViewItem()) return parent->self(); + return nullptr; +} + +} + +#endif diff --git a/hiro/gtk/widget/list-view-cell.hpp b/hiro/gtk/widget/list-view-cell.hpp new file mode 100644 index 00000000..4b32de14 --- /dev/null +++ b/hiro/gtk/widget/list-view-cell.hpp @@ -0,0 +1,18 @@ +#if defined(Hiro_ListView) + +namespace hiro { + +struct pListViewCell : pObject { + Declare(ListViewCell, Object) + + auto setBackgroundColor(Color color) -> void; + auto setForegroundColor(Color color) -> void; + auto setIcon(const image& icon) -> void; + auto setText(const string& text) -> void; + + auto _parent() -> pListViewItem*; +}; + +} + +#endif diff --git a/hiro/gtk/widget/list-view-column.cpp b/hiro/gtk/widget/list-view-column.cpp index 9a718e21..0a6834a7 100644 --- a/hiro/gtk/widget/list-view-column.cpp +++ b/hiro/gtk/widget/list-view-column.cpp @@ -63,6 +63,12 @@ auto pListViewColumn::setEditable(bool editable) -> void { g_object_set(G_OBJECT(gtkCellText), "editable", editable ? TRUE : FALSE, nullptr); } +auto pListViewColumn::setExpandable(bool expandable) -> void { + if(auto parent = _parent()) { + parent->resizeColumns(); + } +} + auto pListViewColumn::setFont(const string& font) -> void { pFont::setFont(gtkHeaderText, font); auto fontDescription = pFont::create(font); @@ -95,10 +101,6 @@ auto pListViewColumn::setResizable(bool resizable) -> void { gtk_tree_view_column_set_resizable(gtkColumn, resizable); } -auto pListViewColumn::setSortable(bool sortable) -> void { - gtk_tree_view_column_set_clickable(gtkColumn, sortable); -} - auto pListViewColumn::setText(const string& text) -> void { gtk_label_set_text(GTK_LABEL(gtkHeaderText), text); } diff --git a/hiro/gtk/widget/list-view-column.hpp b/hiro/gtk/widget/list-view-column.hpp index 70f2be29..df23eb65 100644 --- a/hiro/gtk/widget/list-view-column.hpp +++ b/hiro/gtk/widget/list-view-column.hpp @@ -8,12 +8,12 @@ struct pListViewColumn : pObject { auto setActive() -> void; auto setBackgroundColor(Color color) -> void; auto setEditable(bool editable) -> void; + auto setExpandable(bool expandable) -> void; auto setFont(const string& font) -> void override; auto setForegroundColor(Color color) -> void; auto setHorizontalAlignment(double alignment) -> void; auto setIcon(const image& icon) -> void; auto setResizable(bool resizable) -> void; - auto setSortable(bool sortable) -> void; auto setText(const string& text) -> void; auto setVerticalAlignment(double alignment) -> void; auto setVisible(bool visible) -> void override; diff --git a/hiro/gtk/widget/list-view-item.cpp b/hiro/gtk/widget/list-view-item.cpp index ebf24425..ca597256 100644 --- a/hiro/gtk/widget/list-view-item.cpp +++ b/hiro/gtk/widget/list-view-item.cpp @@ -8,6 +8,15 @@ auto pListViewItem::construct() -> void { auto pListViewItem::destruct() -> void { } +auto pListViewItem::append(sListViewCell cell) -> void { +} + +auto pListViewItem::remove(sListViewCell cell) -> void { +} + +auto pListViewItem::setBackgroundColor(Color color) -> void { +} + auto pListViewItem::setChecked(bool checked) -> void { if(auto parent = _parent()) { gtk_list_store_set(parent->gtkListStore, >kIter, 0, checked, -1); @@ -23,15 +32,7 @@ auto pListViewItem::setFocused() -> void { } } -auto pListViewItem::setIcon(unsigned column, const image& icon) -> void { - if(auto parent = _parent()) { - if(icon) { - auto pixbuf = CreatePixbuf(icon, true); - gtk_list_store_set(parent->gtkListStore, >kIter, 1 + column * 2, pixbuf, -1); - } else { - gtk_list_store_set(parent->gtkListStore, >kIter, 1 + column * 2, nullptr, -1); - } - } +auto pListViewItem::setForegroundColor(Color color) -> void { } auto pListViewItem::setSelected(bool selected) -> void { @@ -47,12 +48,6 @@ auto pListViewItem::setSelected(bool selected) -> void { } } -auto pListViewItem::setText(unsigned column, const string& text) -> void { - if(auto parent = _parent()) { - gtk_list_store_set(parent->gtkListStore, >kIter, 1 + column * 2 + 1, text.data(), -1); - } -} - auto pListViewItem::_parent() -> pListView* { if(auto parent = self().parentListView()) return parent->self(); return nullptr; diff --git a/hiro/gtk/widget/list-view-item.hpp b/hiro/gtk/widget/list-view-item.hpp index 881db09e..0695ba21 100644 --- a/hiro/gtk/widget/list-view-item.hpp +++ b/hiro/gtk/widget/list-view-item.hpp @@ -5,11 +5,13 @@ namespace hiro { struct pListViewItem : pObject { Declare(ListViewItem, Object) + auto append(sListViewCell cell) -> void; + auto remove(sListViewCell cell) -> void; + auto setBackgroundColor(Color color) -> void; auto setChecked(bool checked) -> void; auto setFocused() -> void; - auto setIcon(unsigned column, const image& icon) -> void; + auto setForegroundColor(Color color) -> void; auto setSelected(bool selected) -> void; - auto setText(unsigned column, const string& text) -> void; auto _parent() -> pListView*; diff --git a/hiro/gtk/widget/list-view.cpp b/hiro/gtk/widget/list-view.cpp index 96d9f0d9..5a002b44 100644 --- a/hiro/gtk/widget/list-view.cpp +++ b/hiro/gtk/widget/list-view.cpp @@ -26,12 +26,13 @@ auto pListView::construct() -> void { gtk_widget_show(gtkWidgetChild); setBackgroundColor(state().backgroundColor); + setBatchable(state().batchable); setCheckable(state().checkable); setFont(self().font(true)); setForegroundColor(state().foregroundColor); setGridVisible(state().gridVisible); setHeaderVisible(state().headerVisible); - setMultiSelect(state().multiSelect); + setSortable(state().sortable); g_signal_connect(G_OBJECT(gtkTreeView), "button-press-event", G_CALLBACK(ListView_buttonEvent), (gpointer)this); g_signal_connect(G_OBJECT(gtkTreeView), "button-release-event", G_CALLBACK(ListView_buttonEvent), (gpointer)this); @@ -56,9 +57,12 @@ auto pListView::append(sListViewColumn column) -> void { column->setFont(column->font()); column->setForegroundColor(column->foregroundColor()); column->setHorizontalAlignment(column->horizontalAlignment()); + column->setResizable(column->resizable()); column->setVerticalAlignment(column->verticalAlignment()); setCheckable(state().checkable); + setSortable(state().sortable); _createModel(); + resizeColumns(); gtk_tree_view_set_rules_hint(gtkTreeView, self().columns() >= 2); //two or more columns + checkbutton column } @@ -68,8 +72,18 @@ auto pListView::append(sListViewItem item) -> void { item->setChecked(item->checked()); item->setSelected(item->selected()); for(auto column : range(self().columns())) { - item->setIcon(column, item->state.icon(column, {})); - item->setText(column, item->state.text(column, "")); + if(auto cell = item->cell(column)) { + if(auto self = cell->self()) { + self->setIcon(cell->state.icon); + self->setText(cell->state.text); + } + } + } +} + +auto pListView::checkAll() -> void { + for(auto& item : state().items) { + if(auto delegate = item->self()) delegate->setChecked(true); } } @@ -106,83 +120,61 @@ auto pListView::reset() -> void { gtk_tree_view_set_rules_hint(gtkTreeView, false); } -//column widths: -//< 0 = expanding (consume all remaining space) -// 0 = auto (resize to contents -//> 0 = fixed width auto pListView::resizeColumns() -> void { lock(); - //compute the minimum width required for each column based upon the contents of all rows - vector minimumWidths; - for(auto column : range(self().columns())) { - signed maximumWidth = 1; - if(self().headerVisible()) { - maximumWidth = max(maximumWidth, 8 //margin - + state().columns[column]->state.icon.width - + Font::size(state().columns[column]->font(true), state().columns[column]->state.text).width() - ); + vector widths; + signed minimumWidth = 0; + signed expandable = 0; + for(auto column : range(state().columns)) { + signed width = _width(column); + widths.append(width); + minimumWidth += width; + if(state().columns[column]->expandable()) expandable++; + } + + signed maximumWidth = self().geometry().width() - 6; + if(auto scrollBar = gtk_scrolled_window_get_vscrollbar(gtkScrolledWindow)) { + if(gtk_widget_get_visible(scrollBar)) maximumWidth -= scrollBar->allocation.width; + } + + signed expandWidth = 0; + if(expandable && maximumWidth > minimumWidth) { + expandWidth = (maximumWidth - minimumWidth) / expandable; + } + + for(auto column : range(state().columns)) { + if(auto self = state().columns[column]->self()) { + signed width = widths[column]; + if(self->state().expandable) width += expandWidth; + gtk_tree_view_column_set_fixed_width(self->gtkColumn, width); } - for(auto row : range(self().items())) { - maximumWidth = max(maximumWidth, 8 //margin - + (row == 0 && state().checkable ? 32 : 0) //check box - + state().items[row]->state.icon(column, {}).width - + Font::size(state().columns[column]->font(true), state().items[row]->state.text(column, "")).width() - ); - } - if(!state().columns[column]->visible()) maximumWidth = 1; - minimumWidths.append(maximumWidth); - } - - //subtract the widths of all non-expanding columns from the available widget space - signed expansions = 0; //count the number of expanded columns - signed emptyWidth = pSizable::state().geometry.width() - 5; //margin - for(auto column : range(self().columns())) { - signed width = state().columns[column]->width(); - if(!state().columns[column]->visible()) width = 1; - if(width < 0) { expansions++; continue; } - if(width == 0) width = minimumWidths[column]; - emptyWidth -= width; - } - - //the vertical scroll bar consumes header space when visible; subtract it from available space if needed - auto scrollBar = gtk_scrolled_window_get_vscrollbar(gtkScrolledWindow); - if(scrollBar && gtk_widget_get_visible(scrollBar)) { - emptyWidth -= scrollBar->allocation.width; - } - - //divide remaining space among all expanded columns - if(expansions && emptyWidth >= expansions) emptyWidth /= expansions; - else emptyWidth = 1; - - for(auto column : range(self().columns())) { - signed width = state().columns[column]->width(); - if(!state().columns[column]->visible()) width = 1; - if(width < 0) width = emptyWidth; - if(width == 0) width = minimumWidths[column]; - gtk_tree_view_column_set_fixed_width(_column(column)->gtkColumn, width); } unlock(); } +auto pListView::selectAll() -> void { + for(auto& item : state().items) { + if(auto delegate = item->self()) delegate->setSelected(true); + } +} + auto pListView::setBackgroundColor(Color color) -> void { GdkColor gdkColor = CreateColor(color); gtk_widget_modify_base(gtkWidgetChild, GTK_STATE_NORMAL, color ? &gdkColor : nullptr); } +auto pListView::setBatchable(bool batchable) -> void { + gtk_tree_selection_set_mode(gtkTreeSelection, batchable ? GTK_SELECTION_MULTIPLE : GTK_SELECTION_SINGLE); +} + auto pListView::setCheckable(bool checkable) -> void { if(auto delegate = _column(0)) { gtk_cell_renderer_set_visible(delegate->gtkCellToggle, checkable); } } -auto pListView::setChecked(bool checked) -> void { - for(auto& item : state().items) { - if(auto delegate = item->self()) delegate->setChecked(checked); - } -} - auto pListView::setFocused() -> void { gtk_widget_grab_focus(gtkWidgetChild); } @@ -206,21 +198,60 @@ auto pListView::setHeaderVisible(bool visible) -> void { gtk_tree_view_set_headers_visible(gtkTreeView, visible); } -auto pListView::setMultiSelect(bool multiSelect) -> void { - gtk_tree_selection_set_mode(gtkTreeSelection, multiSelect ? GTK_SELECTION_MULTIPLE : GTK_SELECTION_SINGLE); +auto pListView::setSortable(bool sortable) -> void { + for(auto& column : state().columns) { + if(auto delegate = column->self()) { + gtk_tree_view_column_set_clickable(delegate->gtkColumn, sortable); + } + } } -auto pListView::setSelected(bool selected) -> void { +auto pListView::uncheckAll() -> void { for(auto& item : state().items) { - if(auto delegate = item->self()) delegate->setSelected(selected); + if(auto delegate = item->self()) delegate->setChecked(false); } } +auto pListView::unselectAll() -> void { + for(auto& item : state().items) { + if(auto delegate = item->self()) delegate->setSelected(false); + } +} + +auto pListView::_cellWidth(unsigned _row, unsigned _column) -> unsigned { + unsigned width = 8; //margin + if(state().checkable && _column == 0) width += 32; //checkbox + if(auto item = self().item(_row)) { + if(auto cell = item->cell(_column)) { + if(auto& icon = cell->state.icon) { + width += icon.width + 2; + } + if(auto& text = cell->state.text) { + width += Font::size(cell->font(true), text).width(); + } + } + } + return width; +} + auto pListView::_column(unsigned column) -> pListViewColumn* { if(auto delegate = self().column(column)) return delegate->self(); return nullptr; } +auto pListView::_columnWidth(unsigned _column) -> unsigned { + unsigned width = 8; //margin + if(auto column = self().column(_column)) { + if(auto& icon = column->state.icon) { + width += icon.width + 2; + } + if(auto& text = column->state.text) { + width += Font::size(column->font(true), text).width(); + } + } + return width; +} + auto pListView::_createModel() -> void { gtk_tree_view_set_model(gtkTreeView, nullptr); gtkListStore = nullptr; @@ -256,13 +287,16 @@ auto pListView::_doContext() -> void { auto pListView::_doEdit(GtkCellRendererText* gtkCellRendererText, const char* path, const char* text) -> void { for(auto& column : state().columns) { if(auto delegate = column->self()) { - if(gtkCellRendererText = GTK_CELL_RENDERER_TEXT(delegate->gtkCellText)) { - if(auto item = self().item(decimal(path))) { - if(string{text} != item->text(column->offset())) { - item->setText(column->offset(), text); - if(!locked()) self().doEdit(item, column); + if(gtkCellRendererText == GTK_CELL_RENDERER_TEXT(delegate->gtkCellText)) { + auto row = decimal(path); + if(auto item = self().item(row)) { + if(auto cell = item->cell(column->offset())) { + if(string{text} != cell->state.text) { + cell->setText(text); + if(!locked()) self().doEdit(cell); + } + return; } - return; } } } @@ -277,7 +311,7 @@ auto pListView::_doEvent(GdkEventButton* event) -> signed { //when clicking in empty space below the last list view item; GTK+ does not deselect all items; //below code enables this functionality, to match behavior with all other UI toolkits (and because it's very convenient to have) if(path == nullptr && gtk_tree_selection_count_selected_rows(gtkTreeSelection) > 0) { - self().setSelected({}); + self().unselectAll(); self().doChange(); return true; } @@ -373,6 +407,19 @@ auto pListView::_updateSelected() -> void { if(!locked()) self().doChange(); } +auto pListView::_width(unsigned column) -> unsigned { + if(auto width = state().columns[column]->width()) return width; + unsigned width = 1; + if(!state().columns[column]->visible()) return width; + if(state().headerVisible) { + width = max(width, _columnWidth(column)); + } + for(auto row : range(state().items)) { + width = max(width, _cellWidth(row, column)); + } + return width; +} + } #endif diff --git a/hiro/gtk/widget/list-view.hpp b/hiro/gtk/widget/list-view.hpp index 7f2f7436..852e4298 100644 --- a/hiro/gtk/widget/list-view.hpp +++ b/hiro/gtk/widget/list-view.hpp @@ -7,23 +7,28 @@ struct pListView : pWidget { auto append(sListViewColumn column) -> void; auto append(sListViewItem item) -> void; + auto checkAll() -> void; auto focused() -> bool; auto remove(sListViewColumn column) -> void; auto remove(sListViewItem item) -> void; auto reset() -> void; auto resizeColumns() -> void; + auto selectAll() -> void; auto setBackgroundColor(Color color) -> void; + auto setBatchable(bool batchable) -> void; auto setCheckable(bool checkable) -> void; - auto setChecked(bool checked) -> void; auto setFocused() -> void override; auto setFont(const string& font) -> void override; auto setForegroundColor(Color color) -> void; auto setGridVisible(bool visible) -> void; auto setHeaderVisible(bool visible) -> void; - auto setMultiSelect(bool multiSelect) -> void; - auto setSelected(bool selected) -> void; + auto setSortable(bool sortable) -> void; + auto uncheckAll() -> void; + auto unselectAll() -> void; + auto _cellWidth(unsigned row, unsigned column) -> unsigned; auto _column(unsigned column) -> pListViewColumn*; + auto _columnWidth(unsigned column) -> unsigned; auto _createModel() -> void; auto _doActivate() -> void; auto _doChange() -> void; @@ -34,6 +39,7 @@ struct pListView : pWidget { auto _doMouseMove() -> signed; auto _doToggle(const char* path) -> void; auto _updateSelected() -> void; + auto _width(unsigned column) -> unsigned; GtkScrolledWindow* gtkScrolledWindow = nullptr; GtkWidget* gtkWidgetChild = nullptr; diff --git a/hiro/gtk/widget/radio-button.cpp b/hiro/gtk/widget/radio-button.cpp index 5298a6ef..89020da6 100644 --- a/hiro/gtk/widget/radio-button.cpp +++ b/hiro/gtk/widget/radio-button.cpp @@ -3,7 +3,7 @@ namespace hiro { static auto RadioButton_activate(GtkToggleButton*, pRadioButton* p) -> void { - if(p->_parent().locked()) return; + if(p->groupLocked()) return; bool wasChecked = p->state().checked; p->setChecked(); if(!wasChecked) p->self().doActivate(); @@ -12,7 +12,6 @@ static auto RadioButton_activate(GtkToggleButton*, pRadioButton* p) -> void { auto pRadioButton::construct() -> void { gtkWidget = gtk_toggle_button_new(); - setGroup(state().group); setBordered(state().bordered); setIcon(state().icon); setOrientation(state().orientation); @@ -48,26 +47,34 @@ auto pRadioButton::setBordered(bool bordered) -> void { } auto pRadioButton::setChecked() -> void { - _parent().lock(); - for(auto& weak : state().group) { - if(auto item = weak.acquire()) { - if(item->self()) { - bool checked = item->self() == this; - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(item->self()->gtkWidget), item->state.checked = checked); + if(!self().group()) return; + for(auto& weak : self().group()->state.objects) { + if(auto object = weak.acquire()) { + if(auto radioButton = dynamic_cast(object.data())) { + if(auto self = radioButton->self()) { + self->lock(); + bool checked = self == this; + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->gtkWidget), radioButton->state.checked = checked); + self->unlock(); + } } } } - _parent().unlock(); } -auto pRadioButton::setGroup(const vector& group) -> void { - _parent().lock(); - for(auto& weak : state().group) { - if(auto item = weak.acquire()) { - if(item->self()) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(item->self()->gtkWidget), item->checked()); +auto pRadioButton::setGroup(sGroup group) -> void { + if(!group) return; + for(auto& weak : group->state.objects) { + if(auto object = weak.acquire()) { + if(auto radioButton = dynamic_cast(object.data())) { + if(auto self = radioButton->self()) { + self->lock(); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->gtkWidget), radioButton->checked()); + self->unlock(); + } + } } } - _parent().unlock(); } auto pRadioButton::setIcon(const image& icon) -> void { @@ -91,13 +98,18 @@ auto pRadioButton::setText(const string& text) -> void { setFont(self().font(true)); //gtk_button_set_label() recreates label, which destroys currently assigned font } -auto pRadioButton::_parent() -> pRadioButton& { - if(state().group.size()) { - if(auto item = state().group.first().acquire()) { - if(item->self()) return *item->self(); +auto pRadioButton::groupLocked() const -> bool { + if(auto group = state().group) { + for(auto& weak : group->state.objects) { + if(auto object = weak.acquire()) { + if(auto self = object->self()) { + if(self->locked()) return true; + } + } } + return false; } - return *this; + return locked(); } } diff --git a/hiro/gtk/widget/radio-button.hpp b/hiro/gtk/widget/radio-button.hpp index 2d966883..2be014dd 100644 --- a/hiro/gtk/widget/radio-button.hpp +++ b/hiro/gtk/widget/radio-button.hpp @@ -8,12 +8,12 @@ struct pRadioButton : pWidget { auto minimumSize() const -> Size; auto setBordered(bool bordered) -> void; auto setChecked() -> void; - auto setGroup(const vector& group) -> void; + auto setGroup(sGroup group) -> void; auto setIcon(const image& icon) -> void; auto setOrientation(Orientation orientation) -> void; auto setText(const string& text) -> void; - auto _parent() -> pRadioButton&; + auto groupLocked() const -> bool; }; } diff --git a/hiro/gtk/widget/radio-label.cpp b/hiro/gtk/widget/radio-label.cpp index 222e05de..9681cd29 100644 --- a/hiro/gtk/widget/radio-label.cpp +++ b/hiro/gtk/widget/radio-label.cpp @@ -3,7 +3,7 @@ namespace hiro { static auto RadioLabel_activate(GtkToggleButton*, pRadioLabel* p) -> void { - if(p->_parent().locked()) return; + if(p->groupLocked()) return; bool wasChecked = p->state().checked; p->setChecked(); if(!wasChecked) p->self().doActivate(); @@ -11,8 +11,9 @@ static auto RadioLabel_activate(GtkToggleButton*, pRadioLabel* p) -> void { auto pRadioLabel::construct() -> void { gtkWidget = gtk_radio_button_new_with_label(nullptr, ""); + gtkToggleButton = GTK_TOGGLE_BUTTON(gtkWidget); + gtkRadioButton = GTK_RADIO_BUTTON(gtkWidget); - setGroup(state().group); setText(state().text); g_signal_connect(G_OBJECT(gtkWidget), "toggled", G_CALLBACK(RadioLabel_activate), (gpointer)this); @@ -30,30 +31,29 @@ auto pRadioLabel::minimumSize() const -> Size { } auto pRadioLabel::setChecked() -> void { - _parent().lock(); - for(auto& weak : state().group) { - if(auto item = weak.acquire()) item->state.checked = false; - } - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtkWidget), state().checked = true); - _parent().unlock(); + lock(); + gtk_toggle_button_set_active(gtkToggleButton, true); + unlock(); } -auto pRadioLabel::setGroup(const vector>& group) -> void { - if(&_parent() == this) return; - _parent().lock(); - gtk_radio_button_set_group( - GTK_RADIO_BUTTON(gtkWidget), - gtk_radio_button_get_group(GTK_RADIO_BUTTON(_parent().gtkWidget)) - ); - for(auto& weak : state().group) { - if(auto item = weak.acquire()) { - if(item->self() && item->checked()) { - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(item->self()->gtkWidget), true); - break; +auto pRadioLabel::setGroup(sGroup group) -> void { + if(!group) return; + + maybe gtkRadioButton; + for(auto& weak : group->state.objects) { + if(auto object = weak.acquire()) { + if(auto radioLabel = dynamic_cast(object.data())) { + if(auto self = radioLabel->self()) { + self->lock(); + gtk_radio_button_set_group(self->gtkRadioButton, nullptr); + if(!gtkRadioButton) gtkRadioButton = self->gtkRadioButton; + else gtk_radio_button_set_group(self->gtkRadioButton, gtk_radio_button_get_group(*gtkRadioButton)); + gtk_toggle_button_set_active(self->gtkToggleButton, radioLabel->checked()); + self->unlock(); + } } } } - _parent().unlock(); } auto pRadioLabel::setText(const string& text) -> void { @@ -61,13 +61,18 @@ auto pRadioLabel::setText(const string& text) -> void { setFont(self().font(true)); //gtk_button_set_label() recreates label, which destroys currently assigned font } -auto pRadioLabel::_parent() -> pRadioLabel& { - if(state().group.size()) { - if(auto item = state().group.first().acquire()) { - if(item->self()) return *item->self(); +auto pRadioLabel::groupLocked() const -> bool { + if(auto group = state().group) { + for(auto& weak : group->state.objects) { + if(auto object = weak.acquire()) { + if(auto self = object->self()) { + if(self->locked()) return true; + } + } } + return false; } - return *this; + return locked(); } } diff --git a/hiro/gtk/widget/radio-label.hpp b/hiro/gtk/widget/radio-label.hpp index 7fb8f1cb..25bb71b4 100644 --- a/hiro/gtk/widget/radio-label.hpp +++ b/hiro/gtk/widget/radio-label.hpp @@ -7,10 +7,13 @@ struct pRadioLabel : pWidget { auto minimumSize() const -> Size; auto setChecked() -> void; - auto setGroup(const vector>& group) -> void; + auto setGroup(sGroup group) -> void; auto setText(const string& text) -> void; - auto _parent() -> pRadioLabel&; + auto groupLocked() const -> bool; + + GtkToggleButton* gtkToggleButton = nullptr; + GtkRadioButton* gtkRadioButton = nullptr; }; } diff --git a/hiro/gtk/widget/tab-frame-item.cpp b/hiro/gtk/widget/tab-frame-item.cpp index e2c5dade..3b8f548b 100644 --- a/hiro/gtk/widget/tab-frame-item.cpp +++ b/hiro/gtk/widget/tab-frame-item.cpp @@ -10,6 +10,12 @@ auto pTabFrameItem::destruct() -> void { if(auto layout = state().layout) layout->destruct(); } +auto pTabFrameItem::append(sLayout layout) -> void { +} + +auto pTabFrameItem::remove(sLayout layout) -> void { +} + auto pTabFrameItem::setClosable(bool closable) -> void { if(auto parent = _parent()) { parent->setItemClosable(self().offset(), closable); diff --git a/hiro/gtk/widget/tab-frame-item.hpp b/hiro/gtk/widget/tab-frame-item.hpp index c9d54bbc..495555b0 100644 --- a/hiro/gtk/widget/tab-frame-item.hpp +++ b/hiro/gtk/widget/tab-frame-item.hpp @@ -5,6 +5,8 @@ namespace hiro { struct pTabFrameItem : pObject { Declare(TabFrameItem, Object) + auto append(sLayout layout) -> void; + auto remove(sLayout layout) -> void; auto setClosable(bool closable) -> void; auto setIcon(const image& icon) -> void; auto setMovable(bool movable) -> void; diff --git a/hiro/gtk/widget/tab-frame.cpp b/hiro/gtk/widget/tab-frame.cpp index ffb19e53..a00a1064 100644 --- a/hiro/gtk/widget/tab-frame.cpp +++ b/hiro/gtk/widget/tab-frame.cpp @@ -3,7 +3,9 @@ namespace hiro { static auto TabFrame_change(GtkNotebook* notebook, GtkWidget* page, unsigned position, pTabFrame* p) -> void { - p->state().selected = position; + for(auto& item : p->state().items) item->state.selected = false; + if(auto item = p->self().item(position)) item->state.selected = true; + p->_synchronizeLayout(); if(!p->locked()) p->self().doChange(); } @@ -22,7 +24,10 @@ static auto TabFrame_close(GtkButton* button, pTabFrame* p) -> void { } static auto TabFrame_move(GtkNotebook* notebook, GtkWidget* page, unsigned moveTo, pTabFrame* p) -> void { - p->state().selected = gtk_notebook_get_current_page(notebook); + unsigned position = gtk_notebook_get_current_page(notebook); + for(auto& item : p->state().items) item->state.selected = false; + if(auto item = p->self().item(position)) item->state.selected = true; + maybe moveFrom; for(auto n : range(p->tabs)) { if(page == p->tabs[n].child) { @@ -45,7 +50,6 @@ auto pTabFrame::construct() -> void { tabs.reset(); //todo: memory leak, need to release each tab for(auto& item : state().items) append(item); setEdge(state().edge); - setItemSelected(state().selected); g_signal_connect(G_OBJECT(gtkWidget), "page-reordered", G_CALLBACK(TabFrame_move), (gpointer)this); g_signal_connect(G_OBJECT(gtkWidget), "switch-page", G_CALLBACK(TabFrame_change), (gpointer)this); @@ -87,6 +91,7 @@ auto pTabFrame::append(sTabFrameItem item) -> void { setFont(self().font(true)); setItemMovable(item->offset(), item->movable()); + if(item->selected()) setItemSelected(item->offset()); _synchronizeTab(tabs.size() - 1); setGeometry(self().geometry()); unlock(); @@ -128,7 +133,10 @@ auto pTabFrame::remove(sTabFrameItem item) -> void { } tabs.remove(item->offset()); gtk_notebook_remove_page(GTK_NOTEBOOK(gtkWidget), item->offset()); - state().selected = gtk_notebook_get_current_page(GTK_NOTEBOOK(gtkWidget)); + + unsigned position = gtk_notebook_get_current_page(GTK_NOTEBOOK(gtkWidget)); + for(auto& item : state().items) item->state.selected = false; + if(auto item = self().item(position)) item->state.selected = true; unlock(); } @@ -218,14 +226,12 @@ auto pTabFrame::setVisible(bool visible) -> void { } auto pTabFrame::_synchronizeLayout() -> void { - unsigned position = 0; for(auto& item : state().items) { if(auto layout = item->state.layout) { - if(layout->self()) { - layout->self()->setVisible(layout->visible(true) && position == state().selected); + if(auto self = layout->self()) { + self->setVisible(layout->visible(true) && item->selected()); } } - position++; } } diff --git a/hiro/gtk/window.cpp b/hiro/gtk/window.cpp index 3109f3ec..b36280d3 100644 --- a/hiro/gtk/window.cpp +++ b/hiro/gtk/window.cpp @@ -184,13 +184,16 @@ auto pWindow::construct() -> void { auto pWindow::destruct() -> void { } -auto pWindow::append(shared_pointer menuBar) -> void { +auto pWindow::append(sLayout layout) -> void { +} + +auto pWindow::append(sMenuBar menuBar) -> void { _setMenuEnabled(menuBar->enabled(true)); _setMenuFont(menuBar->font(true)); _setMenuVisible(menuBar->visible(true)); } -auto pWindow::append(shared_pointer statusBar) -> void { +auto pWindow::append(sStatusBar statusBar) -> void { _setStatusEnabled(statusBar->enabled(true)); _setStatusFont(statusBar->font(true)); _setStatusText(statusBar->text()); @@ -215,11 +218,14 @@ auto pWindow::frameMargin() const -> Geometry { }; } -auto pWindow::remove(shared_pointer menuBar) -> void { +auto pWindow::remove(sLayout layout) -> void { +} + +auto pWindow::remove(sMenuBar menuBar) -> void { _setMenuVisible(false); } -auto pWindow::remove(shared_pointer statusBar) -> void { +auto pWindow::remove(sStatusBar statusBar) -> void { _setStatusVisible(false); } diff --git a/hiro/gtk/window.hpp b/hiro/gtk/window.hpp index 55543275..b6f211f0 100644 --- a/hiro/gtk/window.hpp +++ b/hiro/gtk/window.hpp @@ -14,12 +14,14 @@ struct pWindow : pObject { GtkAllocation lastAllocation = {0}; bool onSizePending = false; - auto append(shared_pointer menuBar) -> void; - auto append(shared_pointer statusBar) -> void; + auto append(sLayout layout) -> void; + auto append(sMenuBar menuBar) -> void; + auto append(sStatusBar statusBar) -> void; auto focused() const -> bool override; auto frameMargin() const -> Geometry; - auto remove(shared_pointer menuBar) -> void; - auto remove(shared_pointer statusBar) -> void; + auto remove(sLayout layout) -> void; + auto remove(sMenuBar menuBar) -> void; + auto remove(sStatusBar statusBar) -> void; auto setBackgroundColor(Color color) -> void; auto setDroppable(bool droppable) -> void; auto setEnabled(bool enabled) -> void override; diff --git a/hiro/windows/action/action.cpp b/hiro/windows/action/action.cpp index 8a39da11..0a01d873 100644 --- a/hiro/windows/action/action.cpp +++ b/hiro/windows/action/action.cpp @@ -1,16 +1,46 @@ -namespace phoenix { +#if defined(Hiro_Action) -void pAction::setEnabled(bool enabled) { - if(parentWindow) parentWindow->p.updateMenu(); +namespace hiro { + +auto pAction::construct() -> void { } -void pAction::setVisible(bool visible) { - if(parentWindow) parentWindow->p.updateMenu(); +auto pAction::destruct() -> void { } -void pAction::constructor() { - parentMenu = 0; - parentWindow = 0; +auto pAction::setEnabled(bool enabled) -> void { + _synchronize(); +} + +auto pAction::setVisible(bool visible) -> void { + _synchronize(); +} + +auto pAction::_parentMenu() -> maybe { + if(auto parent = self().parentMenu()) { + if(auto self = parent->self()) return *self; + } + return nothing; +} + +auto pAction::_parentMenuBar() -> maybe { + if(auto parent = self().parentMenuBar(true)) { + if(auto self = parent->self()) return *self; + } + return nothing; +} + +auto pAction::_parentPopupMenu() -> maybe { + if(auto parent = self().parentPopupMenu(true)) { + if(auto self = parent->self()) return *self; + } + return nothing; +} + +auto pAction::_synchronize() -> void { + if(auto parent = _parentMenuBar()) parent->_update(); } } + +#endif diff --git a/hiro/windows/action/action.hpp b/hiro/windows/action/action.hpp new file mode 100644 index 00000000..a7b280f1 --- /dev/null +++ b/hiro/windows/action/action.hpp @@ -0,0 +1,21 @@ +#if defined(Hiro_Action) + +namespace hiro { + +struct pAction : pObject { + Declare(Action, Object) + + auto setEnabled(bool enabled) -> void; + auto setVisible(bool visible) -> void; + + auto _parentMenu() -> maybe; + auto _parentMenuBar() -> maybe; + auto _parentPopupMenu() -> maybe; + auto _synchronize() -> void; + + unsigned position = 0; +}; + +} + +#endif diff --git a/hiro/windows/action/check-item.cpp b/hiro/windows/action/check-item.cpp deleted file mode 100644 index cedea2b3..00000000 --- a/hiro/windows/action/check-item.cpp +++ /dev/null @@ -1,24 +0,0 @@ -namespace phoenix { - -void pCheckItem::setChecked(bool checked) { - if(parentMenu) CheckMenuItem(parentMenu->p.hmenu, id, checked ? MF_CHECKED : MF_UNCHECKED); -} - -void pCheckItem::setText(string text) { - if(parentWindow) parentWindow->p.updateMenu(); -} - -void pCheckItem::constructor() { -} - -void pCheckItem::destructor() { - if(parentMenu) parentMenu->remove(checkItem); -} - -void pCheckItem::onToggle() { - checkItem.state.checked = !checkItem.state.checked; - setChecked(checkItem.state.checked); - if(checkItem.onToggle) checkItem.onToggle(); -} - -} diff --git a/hiro/windows/action/item.cpp b/hiro/windows/action/item.cpp deleted file mode 100644 index 0f9ad74d..00000000 --- a/hiro/windows/action/item.cpp +++ /dev/null @@ -1,37 +0,0 @@ -namespace phoenix { - -void pItem::setImage(const image& image) { - createBitmap(); - if(parentWindow) parentWindow->p.updateMenu(); -} - -void pItem::setText(string text) { - if(parentWindow) parentWindow->p.updateMenu(); -} - -void pItem::constructor() { - createBitmap(); -} - -void pItem::destructor() { - if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } - if(parentMenu) parentMenu->remove(item); -} - -void pItem::createBitmap() { - if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } - - if(item.state.image.width && item.state.image.height) { - nall::image nallImage = item.state.image; - nallImage.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); - nallImage.alphaBlend(GetSysColor(COLOR_MENU)); //Windows does not alpha blend menu icons properly (leaves black outline) - nallImage.scale(GetSystemMetrics(SM_CXMENUCHECK), GetSystemMetrics(SM_CYMENUCHECK), Interpolation::Linear); - hbitmap = CreateBitmap(nallImage); - } -} - -void pItem::onActivate() { - if(item.onActivate) item.onActivate(); -} - -} diff --git a/hiro/windows/action/menu-check-item.cpp b/hiro/windows/action/menu-check-item.cpp new file mode 100644 index 00000000..3421432e --- /dev/null +++ b/hiro/windows/action/menu-check-item.cpp @@ -0,0 +1,29 @@ +#if defined(Hiro_MenuCheckItem) + +namespace hiro { + +auto pMenuCheckItem::construct() -> void { +} + +auto pMenuCheckItem::destruct() -> void { +} + +auto pMenuCheckItem::setChecked(bool checked) -> void { + if(auto menu = _parentMenu()) { + CheckMenuItem(menu->hmenu, position, MF_BYPOSITION | (checked ? MF_CHECKED : MF_UNCHECKED)); + } +} + +auto pMenuCheckItem::setText(const string& text) -> void { + _synchronize(); +} + +auto pMenuCheckItem::onToggle() -> void { + state().checked = !state().checked; + setChecked(state().checked); + self().doToggle(); +} + +} + +#endif diff --git a/hiro/windows/action/menu-check-item.hpp b/hiro/windows/action/menu-check-item.hpp new file mode 100644 index 00000000..f3585a37 --- /dev/null +++ b/hiro/windows/action/menu-check-item.hpp @@ -0,0 +1,16 @@ +#if defined(Hiro_MenuCheckItem) + +namespace hiro { + +struct pMenuCheckItem : pAction { + Declare(MenuCheckItem, Action) + + auto setChecked(bool checked) -> void; + auto setText(const string& text) -> void; + + auto onToggle() -> void; +}; + +} + +#endif diff --git a/hiro/windows/action/menu-item.cpp b/hiro/windows/action/menu-item.cpp new file mode 100644 index 00000000..d01b3bc7 --- /dev/null +++ b/hiro/windows/action/menu-item.cpp @@ -0,0 +1,39 @@ +#if defined(Hiro_MenuItem) + +namespace hiro { + +auto pMenuItem::construct() -> void { + _createBitmap(); +} + +auto pMenuItem::destruct() -> void { + if(hbitmap) { DeleteObject(hbitmap); hbitmap = nullptr; } +} + +auto pMenuItem::setIcon(const image& icon) -> void { + _createBitmap(); + _synchronize(); +} + +auto pMenuItem::setText(const string& text) -> void { + _synchronize(); +} + +auto pMenuItem::onActivate() -> void { + self().doActivate(); +} + +auto pMenuItem::_createBitmap() -> void { + if(hbitmap) { DeleteObject(hbitmap); hbitmap = nullptr; } + + if(auto icon = state().icon) { + icon.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); + icon.alphaBlend(GetSysColor(COLOR_MENU)); //Windows does not alpha blend menu icons properly (leaves black outline) + icon.scale(GetSystemMetrics(SM_CXMENUCHECK), GetSystemMetrics(SM_CYMENUCHECK), Interpolation::Linear); + hbitmap = CreateBitmap(icon); + } +} + +} + +#endif diff --git a/hiro/windows/action/menu-item.hpp b/hiro/windows/action/menu-item.hpp new file mode 100644 index 00000000..636eef4a --- /dev/null +++ b/hiro/windows/action/menu-item.hpp @@ -0,0 +1,20 @@ +#if defined(Hiro_MenuItem) + +namespace hiro { + +struct pMenuItem : pAction { + Declare(MenuItem, Action) + + auto setIcon(const image& icon) -> void; + auto setText(const string& text) -> void; + + auto onActivate() -> void; + + auto _createBitmap() -> void; + + HBITMAP hbitmap = 0; +}; + +} + +#endif diff --git a/hiro/windows/action/menu-radio-item.cpp b/hiro/windows/action/menu-radio-item.cpp new file mode 100644 index 00000000..56de8b3d --- /dev/null +++ b/hiro/windows/action/menu-radio-item.cpp @@ -0,0 +1,49 @@ +#if defined(Hiro_MenuRadioItem) + +namespace hiro { + +auto pMenuRadioItem::construct() -> void { +} + +auto pMenuRadioItem::destruct() -> void { +} + +auto pMenuRadioItem::setChecked() -> void { + if(auto group = self().group()) { + for(auto& weak : group->state.objects) { + if(auto object = weak.acquire()) { + if(auto menuRadioItem = dynamic_cast(object.data())) { + if(auto self = menuRadioItem->self()) { + if(auto menu = self->_parentMenu()) { + //CheckMenuRadioItem takes: lo, hi, id; checking only id when lo <= id <= hi + //hiro does not force IDs to be linear, so to uncheck id, we use: lo == hi == id + 1 (out of range) + //to check id, we use: lo == hi == id (only ID, but in range) + CheckMenuRadioItem( + menu->hmenu, + self->position, self->position, self->position + (position != self->position), + MF_BYPOSITION + ); + } + } + } + } + } + } +} + +auto pMenuRadioItem::setGroup(sGroup group) -> void { +} + +auto pMenuRadioItem::setText(const string& text) -> void { + _synchronize(); +} + +auto pMenuRadioItem::onActivate() -> void { + if(state().checked) return; + self().setChecked(); + self().doActivate(); +} + +} + +#endif diff --git a/hiro/windows/action/menu-radio-item.hpp b/hiro/windows/action/menu-radio-item.hpp new file mode 100644 index 00000000..f30ace59 --- /dev/null +++ b/hiro/windows/action/menu-radio-item.hpp @@ -0,0 +1,17 @@ +#if defined(Hiro_MenuRadioItem) + +namespace hiro { + +struct pMenuRadioItem : pAction { + Declare(MenuRadioItem, Action) + + auto setChecked() -> void; + auto setGroup(sGroup group) -> void override; + auto setText(const string& text) -> void; + + auto onActivate() -> void; +}; + +} + +#endif diff --git a/hiro/windows/action/menu-separator.cpp b/hiro/windows/action/menu-separator.cpp new file mode 100644 index 00000000..15602b32 --- /dev/null +++ b/hiro/windows/action/menu-separator.cpp @@ -0,0 +1,13 @@ +#if defined(Hiro_MenuSeparator) + +namespace hiro { + +auto pMenuSeparator::construct() -> void { +} + +auto pMenuSeparator::destruct() -> void { +} + +} + +#endif diff --git a/hiro/windows/action/menu-separator.hpp b/hiro/windows/action/menu-separator.hpp new file mode 100644 index 00000000..a94dbf6f --- /dev/null +++ b/hiro/windows/action/menu-separator.hpp @@ -0,0 +1,11 @@ +#if defined(Hiro_MenuSeparator) + +namespace hiro { + +struct pMenuSeparator : pAction { + Declare(MenuSeparator, Action) +}; + +} + +#endif diff --git a/hiro/windows/action/menu.cpp b/hiro/windows/action/menu.cpp index a44457b7..5d7d1eb0 100644 --- a/hiro/windows/action/menu.cpp +++ b/hiro/windows/action/menu.cpp @@ -1,113 +1,124 @@ -namespace phoenix { +#if defined(Hiro_Menu) -void pMenu::append(Action& action) { - action.p.parentMenu = &menu; - if(parentWindow) parentWindow->p.updateMenu(); +namespace hiro { + +auto pMenu::construct() -> void { + _createBitmap(); } -void pMenu::remove(Action& action) { - if(parentWindow) parentWindow->p.updateMenu(); - action.p.parentMenu = 0; +auto pMenu::destruct() -> void { + if(hbitmap) { DeleteObject(hbitmap); hbitmap = nullptr; } + if(hmenu) { DestroyMenu(hmenu); hmenu = nullptr; } } -void pMenu::setImage(const image& image) { - createBitmap(); - if(parentWindow) parentWindow->p.updateMenu(); +auto pMenu::append(sAction action) -> void { + _synchronize(); } -void pMenu::setText(string text) { - if(parentWindow) parentWindow->p.updateMenu(); +auto pMenu::remove(sAction action) -> void { + _synchronize(); } -void pMenu::constructor() { - hmenu = 0; - createBitmap(); +auto pMenu::setIcon(const image& icon) -> void { + _createBitmap(); + _synchronize(); } -void pMenu::destructor() { - if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } - if(parentMenu) { - parentMenu->remove(menu); - } else if(parentWindow) { - //belongs to window's main menubar - parentWindow->remove(menu); - } +auto pMenu::setText(const string& text) -> void { + _synchronize(); } -void pMenu::createBitmap() { +auto pMenu::_createBitmap() -> void { if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } - if(menu.state.image.width && menu.state.image.height) { - nall::image nallImage = menu.state.image; - nallImage.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); - nallImage.alphaBlend(GetSysColor(COLOR_MENU)); //Windows does not alpha blend menu icons properly (leaves black outline) - nallImage.scale(GetSystemMetrics(SM_CXMENUCHECK), GetSystemMetrics(SM_CYMENUCHECK), Interpolation::Linear); - hbitmap = CreateBitmap(nallImage); + if(auto icon = state().icon) { + icon.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); + icon.alphaBlend(GetSysColor(COLOR_MENU)); //Windows does not alpha blend menu icons properly (leaves black outline) + icon.scale(GetSystemMetrics(SM_CXMENUCHECK), GetSystemMetrics(SM_CYMENUCHECK), Interpolation::Linear); + hbitmap = CreateBitmap(icon); } } //Windows actions lack the ability to toggle visibility. //To support this, menus must be destroyed and recreated when toggling any action's visibility. -void pMenu::update(Window& parentWindow, Menu* parentMenu) { - this->parentMenu = parentMenu; - this->parentWindow = &parentWindow; - +auto pMenu::_update() -> void { if(hmenu) DestroyMenu(hmenu); hmenu = CreatePopupMenu(); - for(auto& action : menu.state.action) { - action.p.parentMenu = &menu; - action.p.parentWindow = &parentWindow; + MENUINFO mi{sizeof(MENUINFO)}; + mi.fMask = MIM_STYLE; + mi.dwStyle = MNS_NOTIFYBYPOS; //| MNS_MODELESS; + SetMenuInfo(hmenu, &mi); - unsigned enabled = action.state.enabled ? 0 : MF_GRAYED; - if(dynamic_cast(&action)) { - Menu& item = (Menu&)action; - if(action.state.visible) { - item.p.update(parentWindow, &menu); - AppendMenu(hmenu, MF_STRING | MF_POPUP | enabled, (UINT_PTR)item.p.hmenu, utf16_t(item.state.text)); + unsigned position = 0; - if(item.state.image.width && item.state.image.height) { - MENUITEMINFO mii = {sizeof(MENUITEMINFO)}; + for(auto& action : state().actions) { + if(!action->self()) continue; + action->self()->position = position; + unsigned enabled = action->enabled() ? 0 : MF_GRAYED; + + MENUITEMINFO mii{sizeof(MENUITEMINFO)}; + mii.fMask = MIIM_DATA; + mii.dwItemData = (ULONG_PTR)action.data(); + + if(auto menu = dynamic_cast(action.data())) { + if(menu->visible()) { + menu->self()->_update(); + AppendMenu(hmenu, MF_STRING | MF_POPUP | enabled, (UINT_PTR)menu->self()->hmenu, utf16_t(menu->text())); + if(auto bitmap = menu->self()->hbitmap) { //Windows XP and below displays MIIM_BITMAP + hbmpItem in its own column (separate from check/radio marks) //this causes too much spacing, so use a custom checkmark image instead - mii.fMask = MIIM_CHECKMARKS; - mii.hbmpUnchecked = item.p.hbitmap; - SetMenuItemInfo(hmenu, (UINT_PTR)item.p.hmenu, FALSE, &mii); + mii.fMask |= MIIM_CHECKMARKS; + mii.hbmpUnchecked = bitmap; } + SetMenuItemInfo(hmenu, position++, true, &mii); } - } else if(dynamic_cast(&action)) { - Separator& item = (Separator&)action; - if(action.state.visible) { - AppendMenu(hmenu, MF_SEPARATOR | enabled, item.p.id, L""); - } - } else if(dynamic_cast(&action)) { - Item& item = (Item&)action; - if(action.state.visible) { - AppendMenu(hmenu, MF_STRING | enabled, item.p.id, utf16_t(item.state.text)); - - if(item.state.image.width && item.state.image.height) { - MENUITEMINFO mii = { sizeof(MENUITEMINFO) }; - //Windows XP and below displays MIIM_BITMAP + hbmpItem in its own column (separate from check/radio marks) - //this causes too much spacing, so use a custom checkmark image instead - mii.fMask = MIIM_CHECKMARKS; - mii.hbmpUnchecked = item.p.hbitmap; - SetMenuItemInfo(hmenu, item.p.id, FALSE, &mii); - } - } - } else if(dynamic_cast(&action)) { - CheckItem& item = (CheckItem&)action; - if(action.state.visible) { - AppendMenu(hmenu, MF_STRING | enabled, item.p.id, utf16_t(item.state.text)); - } - if(item.state.checked) item.setChecked(); - } else if(dynamic_cast(&action)) { - RadioItem& item = (RadioItem&)action; - if(action.state.visible) { - AppendMenu(hmenu, MF_STRING | enabled, item.p.id, utf16_t(item.state.text)); - } - if(item.state.checked) item.setChecked(); } + + #if defined(Hiro_MenuSeparator) + else if(auto menuSeparator = dynamic_cast(action.data())) { + if(menuSeparator->visible()) { + AppendMenu(hmenu, MF_SEPARATOR | enabled, position, L""); + SetMenuItemInfo(hmenu, position++, true, &mii); + } + } + #endif + + #if defined(Hiro_MenuItem) + else if(auto menuItem = dynamic_cast(action.data())) { + if(menuItem->visible()) { + AppendMenu(hmenu, MF_STRING | enabled, position, utf16_t(menuItem->text())); + if(auto bitmap = menuItem->self()->hbitmap) { + mii.fMask |= MIIM_CHECKMARKS; + mii.hbmpUnchecked = bitmap; + } + SetMenuItemInfo(hmenu, position++, true, &mii); + } + } + #endif + + #if defined(Hiro_MenuCheckItem) + else if(auto menuCheckItem = dynamic_cast(action.data())) { + if(menuCheckItem->visible()) { + AppendMenu(hmenu, MF_STRING | enabled, position, utf16_t(menuCheckItem->text())); + SetMenuItemInfo(hmenu, position++, true, &mii); + if(menuCheckItem->checked()) menuCheckItem->setChecked(); + } + } + #endif + + #if defined(Hiro_MenuRadioItem) + else if(auto menuRadioItem = dynamic_cast(action.data())) { + if(menuRadioItem->visible()) { + AppendMenu(hmenu, MF_STRING | enabled, position, utf16_t(menuRadioItem->text())); + SetMenuItemInfo(hmenu, position++, true, &mii); + if(menuRadioItem->checked()) menuRadioItem->setChecked(); + } + } + #endif } } } + +#endif diff --git a/hiro/windows/action/menu.hpp b/hiro/windows/action/menu.hpp new file mode 100644 index 00000000..b8caee56 --- /dev/null +++ b/hiro/windows/action/menu.hpp @@ -0,0 +1,22 @@ +#if defined(Hiro_Menu) + +namespace hiro { + +struct pMenu : pAction { + Declare(Menu, Action) + + auto append(sAction action) -> void; + auto remove(sAction action) -> void; + auto setIcon(const image& icon) -> void; + auto setText(const string& text) -> void; + + auto _createBitmap() -> void; + auto _update() -> void; + + HMENU hmenu = 0; + HBITMAP hbitmap = 0; +}; + +} + +#endif diff --git a/hiro/windows/action/radio-item.cpp b/hiro/windows/action/radio-item.cpp deleted file mode 100644 index 22f48134..00000000 --- a/hiro/windows/action/radio-item.cpp +++ /dev/null @@ -1,32 +0,0 @@ -namespace phoenix { - -void pRadioItem::setChecked() { - for(auto &item : radioItem.state.group) { - //CheckMenuRadioItem takes: lo, hi, id; checking only id when lo <= id <= hi - //phoenix does not force IDs to be linear, so to uncheck id, we use: lo == hi == id + 1 (out of range) - //to check id, we use: lo == hi == id (only ID, but in range) - if(item.p.parentMenu) CheckMenuRadioItem(item.p.parentMenu->p.hmenu, item.p.id, item.p.id, item.p.id + (id != item.p.id), MF_BYCOMMAND); - } -} - -void pRadioItem::setGroup(const group& group) { -} - -void pRadioItem::setText(string text) { - if(parentWindow) parentWindow->p.updateMenu(); -} - -void pRadioItem::constructor() { -} - -void pRadioItem::destructor() { - if(parentMenu) parentMenu->remove(radioItem); -} - -void pRadioItem::onActivate() { - if(radioItem.state.checked) return; - radioItem.setChecked(); - if(radioItem.onActivate) radioItem.onActivate(); -} - -} diff --git a/hiro/windows/action/separator.cpp b/hiro/windows/action/separator.cpp deleted file mode 100644 index c3be0e15..00000000 --- a/hiro/windows/action/separator.cpp +++ /dev/null @@ -1,10 +0,0 @@ -namespace phoenix { - -void pSeparator::constructor() { -} - -void pSeparator::destructor() { - if(parentMenu) parentMenu->remove(separator); -} - -} diff --git a/hiro/windows/application.cpp b/hiro/windows/application.cpp index 1ce366d0..b3d174df 100644 --- a/hiro/windows/application.cpp +++ b/hiro/windows/application.cpp @@ -1,14 +1,16 @@ -namespace phoenix { +#if defined(Hiro_Application) -static bool Application_keyboardProc(HWND, UINT, WPARAM, LPARAM); -static void Application_processDialogMessage(MSG&); -static LRESULT CALLBACK Application_windowProc(HWND, UINT, WPARAM, LPARAM); +namespace hiro { -void pApplication::run() { +static auto Application_keyboardProc(HWND, UINT, WPARAM, LPARAM) -> bool; +static auto Application_processDialogMessage(MSG&) -> void; +static auto CALLBACK Application_windowProc(HWND, UINT, WPARAM, LPARAM) -> LRESULT; + +auto pApplication::run() -> void { MSG msg; - if(Application::main) { - while(applicationState.quit == false) { - Application::main(); + if(Application::state.onMain) { + while(!Application::state.quit) { + Application::doMain(); processEvents(); } } else { @@ -19,12 +21,12 @@ void pApplication::run() { } } -bool pApplication::pendingEvents() { +auto pApplication::pendingEvents() -> bool { MSG msg; return PeekMessage(&msg, 0, 0, 0, PM_NOREMOVE); } -void pApplication::processEvents() { +auto pApplication::processEvents() -> void { while(pendingEvents()) { MSG msg; if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { @@ -33,7 +35,7 @@ void pApplication::processEvents() { } } -void Application_processDialogMessage(MSG& msg) { +auto Application_processDialogMessage(MSG& msg) -> void { if(msg.message == WM_KEYDOWN || msg.message == WM_KEYUP || msg.message == WM_SYSKEYDOWN || msg.message == WM_SYSKEYUP) { if(Application_keyboardProc(msg.hwnd, msg.message, msg.wParam, msg.lParam)) { @@ -48,15 +50,17 @@ void Application_processDialogMessage(MSG& msg) { } } -void pApplication::quit() { +auto pApplication::quit() -> void { PostQuitMessage(0); } -void pApplication::initialize() { +auto pApplication::initialize() -> void { CoInitialize(0); InitCommonControls(); WNDCLASS wc; + + #if defined(Hiro_Window) wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); @@ -64,11 +68,27 @@ void pApplication::initialize() { wc.hIcon = LoadIcon(GetModuleHandle(0), MAKEINTRESOURCE(2)); wc.hInstance = GetModuleHandle(0); wc.lpfnWndProc = Application_windowProc; - wc.lpszClassName = L"phoenix_window"; + wc.lpszClassName = L"hiroWindow"; wc.lpszMenuName = 0; wc.style = CS_HREDRAW | CS_VREDRAW; RegisterClass(&wc); + #endif + #if defined(Hiro_PopupMenu) + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); + wc.hCursor = LoadCursor(0, IDC_ARROW); + wc.hIcon = LoadIcon(GetModuleHandle(0), MAKEINTRESOURCE(2)); + wc.hInstance = GetModuleHandle(0); + wc.lpfnWndProc = Menu_windowProc; + wc.lpszClassName = L"hiroPopupMenu"; + wc.lpszMenuName = 0; + wc.style = CS_HREDRAW | CS_VREDRAW; + RegisterClass(&wc); + #endif + + #if defined(Hiro_Canvas) wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); @@ -76,11 +96,13 @@ void pApplication::initialize() { wc.hIcon = LoadIcon(0, IDI_APPLICATION); wc.hInstance = GetModuleHandle(0); wc.lpfnWndProc = Canvas_windowProc; - wc.lpszClassName = L"phoenix_canvas"; + wc.lpszClassName = L"hiroCanvas"; wc.lpszMenuName = 0; wc.style = CS_HREDRAW | CS_VREDRAW; RegisterClass(&wc); + #endif + #if defined(Hiro_Label) wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); @@ -88,11 +110,13 @@ void pApplication::initialize() { wc.hIcon = LoadIcon(0, IDI_APPLICATION); wc.hInstance = GetModuleHandle(0); wc.lpfnWndProc = Label_windowProc; - wc.lpszClassName = L"phoenix_label"; + wc.lpszClassName = L"hiroLabel"; wc.lpszMenuName = 0; wc.style = CS_HREDRAW | CS_VREDRAW; RegisterClass(&wc); + #endif + #if defined(Hiro_Viewport) wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hbrBackground = CreateSolidBrush(RGB(0, 0, 0)); @@ -100,76 +124,80 @@ void pApplication::initialize() { wc.hIcon = LoadIcon(0, IDI_APPLICATION); wc.hInstance = GetModuleHandle(0); wc.lpfnWndProc = Viewport_windowProc; - wc.lpszClassName = L"phoenix_viewport"; + wc.lpszClassName = L"hiroViewport"; wc.lpszMenuName = 0; wc.style = CS_HREDRAW | CS_VREDRAW; RegisterClass(&wc); + #endif - settings = new Settings; pKeyboard::initialize(); } -static bool Application_keyboardProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { +static auto Application_keyboardProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> bool { if(msg != WM_KEYDOWN && msg != WM_SYSKEYDOWN && msg != WM_KEYUP && msg != WM_SYSKEYUP) return false; GUITHREADINFO info; memset(&info, 0, sizeof(GUITHREADINFO)); info.cbSize = sizeof(GUITHREADINFO); GetGUIThreadInfo(GetCurrentThreadId(), &info); - Object* object = (Object*)GetWindowLongPtr(info.hwndFocus, GWLP_USERDATA); - if(object == nullptr) return false; + auto object = (mObject*)GetWindowLongPtr(info.hwndFocus, GWLP_USERDATA); + if(!object) return false; - if(dynamic_cast(object)) { - Window& window = (Window&)*object; - if(pWindow::modal.size() > 0 && !pWindow::modal.find(&window.p)) return false; - Keyboard::Keycode keysym = Keysym(wparam, lparam); - if(keysym != Keyboard::Keycode::None) { - if((msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN) && window.onKeyPress) window.onKeyPress(keysym); - if((msg == WM_KEYUP || msg == WM_SYSKEYUP) && window.onKeyRelease) window.onKeyRelease(keysym); + if(auto window = dynamic_cast(object)) { + if(pWindow::modal && !pWindow::modal.find(window->self())) return false; + + if(auto code = pKeyboard::_translate(wparam, lparam)) { + if(msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN) window->doKeyPress(code); + if(msg == WM_KEYUP || msg == WM_SYSKEYUP) window->doKeyRelease(code); } + return false; } if(msg == WM_KEYDOWN) { - if(dynamic_cast(object)) { - ListView& listView = (ListView&)*object; + if(0); + + #if defined(Hiro_ListView) + else if(auto listView = dynamic_cast(object)) { if(wparam == VK_RETURN) { - if(listView.selected()) return true; //returning true generates LVN_ITEMACTIVATE message + if(listView->selected()) return true; //returning true generates LVN_ITEMACTIVATE message } - } else if(dynamic_cast(object)) { - LineEdit& lineEdit = (LineEdit&)*object; + } + #endif + + #if defined(Hiro_LineEdit) + else if(auto lineEdit = dynamic_cast(object)) { if(wparam == VK_RETURN) { - if(lineEdit.onActivate) lineEdit.onActivate(); + lineEdit->doActivate(); } - } else if(dynamic_cast(object)) { - TextEdit& textEdit = (TextEdit&)*object; + } + #endif + + #if defined(Hiro_TextEdit) + else if(auto textEdit = dynamic_cast(object)) { if(wparam == 'A' && GetKeyState(VK_CONTROL) < 0) { //Ctrl+A = select all text //note: this is not a standard accelerator on Windows - Edit_SetSel(textEdit.p.hwnd, 0, ~0); + Edit_SetSel(textEdit->self()->hwnd, 0, ~0); return true; } else if(wparam == 'V' && GetKeyState(VK_CONTROL) < 0) { //Ctrl+V = paste text //note: this formats Unix (LF) and OS9 (CR) line-endings to Windows (CR+LF) line-endings //this is necessary as the EDIT control only supports Windows line-endings OpenClipboard(hwnd); - HANDLE handle = GetClipboardData(CF_UNICODETEXT); - if(handle) { - wchar_t* text = (wchar_t*)GlobalLock(handle); - if(text) { + if(auto handle = GetClipboardData(CF_UNICODETEXT)) { + if(auto text = (wchar_t*)GlobalLock(handle)) { string data = (const char*)utf8_t(text); data.replace("\r\n", "\n"); data.replace("\r", "\n"); data.replace("\n", "\r\n"); GlobalUnlock(handle); utf16_t output(data); - HGLOBAL resource = GlobalAlloc(GMEM_MOVEABLE, (wcslen(output) + 1) * sizeof(wchar_t)); - if(resource) { - wchar_t* write = (wchar_t*)GlobalLock(resource); - if(write) { + if(auto resource = GlobalAlloc(GMEM_MOVEABLE, (wcslen(output) + 1) * sizeof(wchar_t))) { + if(auto write = (wchar_t*)GlobalLock(resource)) { wcscpy(write, output); GlobalUnlock(write); - if(SetClipboardData(CF_UNICODETEXT, resource) == FALSE) { + if(SetClipboardData(CF_UNICODETEXT, resource) == NULL) { GlobalFree(resource); } } @@ -180,40 +208,45 @@ static bool Application_keyboardProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM return false; } } + #endif } return false; } -/*case WM_GETMINMAXINFO: { - MINMAXINFO* mmi = (MINMAXINFO*)lparam; - mmi->ptMinTrackSize.x = 256 + window.p.frameMargin().width; - mmi->ptMinTrackSize.y = 256 + window.p.frameMargin().height; - return TRUE; - break; - }*/ +/* +case WM_GETMINMAXINFO: { + MINMAXINFO* mmi = (MINMAXINFO*)lparam; + mmi->ptMinTrackSize.x = 256 + window.p.frameMargin().width; + mmi->ptMinTrackSize.y = 256 + window.p.frameMargin().height; + return TRUE; + break; +} +*/ -static LRESULT CALLBACK Application_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { - Object* object = (Object*)GetWindowLongPtr(hwnd, GWLP_USERDATA); - if(object == nullptr) return DefWindowProc(hwnd, msg, wparam, lparam); - Window& window = dynamic_cast(object) ? (Window&)*object : *((Widget*)object)->Sizable::state.window; +static auto CALLBACK Application_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT { + auto object = (mObject*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if(!object) return DefWindowProc(hwnd, msg, wparam, lparam); + auto& window = dynamic_cast(object) ? (mWindow&)*object : *object->parentWindow(true); bool process = true; - if(!pWindow::modal.empty() && !pWindow::modal.find(&window.p)) process = false; - if(applicationState.quit) process = false; - if(process == false) return DefWindowProc(hwnd, msg, wparam, lparam); + if(pWindow::modal && !pWindow::modal.find(window.self())) process = false; + if(Application::state.quit) process = false; + if(!process) return DefWindowProc(hwnd, msg, wparam, lparam); switch(msg) { - case WM_CLOSE: window.p.onClose(); return TRUE; - case WM_MOVE: window.p.onMove(); break; - case WM_SIZE: window.p.onSize(); break; - case WM_DROPFILES: window.p.onDrop(wparam); return FALSE; - case WM_ERASEBKGND: if(window.p.onEraseBackground()) return true; break; - case WM_ENTERMENULOOP: case WM_ENTERSIZEMOVE: window.p.onModalBegin(); return FALSE; - case WM_EXITMENULOOP: case WM_EXITSIZEMOVE: window.p.onModalEnd(); return FALSE; + case WM_CLOSE: window.self()->onClose(); return TRUE; + case WM_MOVE: window.self()->onMove(); break; + case WM_SIZE: window.self()->onSize(); break; + case WM_DROPFILES: window.self()->onDrop(wparam); return FALSE; + case WM_ERASEBKGND: if(window.self()->onEraseBackground()) return true; break; + case WM_ENTERMENULOOP: case WM_ENTERSIZEMOVE: window.self()->onModalBegin(); return FALSE; + case WM_EXITMENULOOP: case WM_EXITSIZEMOVE: window.self()->onModalEnd(); return FALSE; } return Shared_windowProc(DefWindowProc, hwnd, msg, wparam, lparam); } } + +#endif diff --git a/hiro/windows/application.hpp b/hiro/windows/application.hpp new file mode 100644 index 00000000..8e5b1f83 --- /dev/null +++ b/hiro/windows/application.hpp @@ -0,0 +1,16 @@ +#if defined(Hiro_Application) + +namespace hiro { + +struct pApplication { + static auto run() -> void; + static auto pendingEvents() -> bool; + static auto processEvents() -> void; + static auto quit() -> void; + + static auto initialize() -> void; +}; + +} + +#endif diff --git a/hiro/windows/browser-window.cpp b/hiro/windows/browser-window.cpp index 93b2ba6b..eef3f47e 100644 --- a/hiro/windows/browser-window.cpp +++ b/hiro/windows/browser-window.cpp @@ -1,6 +1,8 @@ -namespace phoenix { +#if defined(Hiro_BrowserWindow) -static int CALLBACK BrowserWindowCallbackProc(HWND hwnd, UINT msg, LPARAM lparam, LPARAM lpdata) { +namespace hiro { + +static auto CALLBACK BrowserWindowCallbackProc(HWND hwnd, UINT msg, LPARAM lparam, LPARAM lpdata) -> signed { if(msg == BFFM_INITIALIZED) { if(lpdata) { auto state = (BrowserWindow::State*)lpdata; @@ -12,7 +14,7 @@ static int CALLBACK BrowserWindowCallbackProc(HWND hwnd, UINT msg, LPARAM lparam return 0; } -static string BrowserWindow_fileDialog(bool save, BrowserWindow::State& state) { +static auto BrowserWindow_fileDialog(bool save, BrowserWindow::State& state) -> string { string path = string{state.path}.replace("/", "\\"); string filters; @@ -46,7 +48,7 @@ static string BrowserWindow_fileDialog(bool save, BrowserWindow::State& state) { OPENFILENAME ofn; memset(&ofn, 0, sizeof(OPENFILENAME)); ofn.lStructSize = sizeof(OPENFILENAME); - ofn.hwndOwner = state.parent ? state.parent->p.hwnd : 0; + ofn.hwndOwner = state.parent ? state.parent->self()->hwnd : 0; ofn.lpstrFilter = wfilters; ofn.lpstrInitialDir = wpath; ofn.lpstrFile = wname; @@ -62,11 +64,11 @@ static string BrowserWindow_fileDialog(bool save, BrowserWindow::State& state) { return name; } -string pBrowserWindow::directory(BrowserWindow::State& state) { +auto pBrowserWindow::directory(BrowserWindow::State& state) -> string { wchar_t wname[PATH_MAX + 1] = L""; BROWSEINFO bi; - bi.hwndOwner = state.parent ? state.parent->p.hwnd : 0; + bi.hwndOwner = state.parent ? state.parent->self()->hwnd : 0; bi.pidlRoot = NULL; bi.pszDisplayName = wname; bi.lpszTitle = L"\nChoose a directory:"; @@ -94,12 +96,14 @@ string pBrowserWindow::directory(BrowserWindow::State& state) { return name; } -string pBrowserWindow::open(BrowserWindow::State& state) { +auto pBrowserWindow::open(BrowserWindow::State& state) -> string { return BrowserWindow_fileDialog(0, state); } -string pBrowserWindow::save(BrowserWindow::State& state) { +auto pBrowserWindow::save(BrowserWindow::State& state) -> string { return BrowserWindow_fileDialog(1, state); } } + +#endif diff --git a/hiro/windows/browser-window.hpp b/hiro/windows/browser-window.hpp new file mode 100644 index 00000000..80e561b1 --- /dev/null +++ b/hiro/windows/browser-window.hpp @@ -0,0 +1,13 @@ +#if defined(Hiro_BrowserWindow) + +namespace hiro { + +struct pBrowserWindow { + static auto directory(BrowserWindow::State& state) -> string; + static auto open(BrowserWindow::State& state) -> string; + static auto save(BrowserWindow::State& state) -> string; +}; + +} + +#endif diff --git a/hiro/windows/desktop.cpp b/hiro/windows/desktop.cpp index 1d06194f..2a5f56fb 100644 --- a/hiro/windows/desktop.cpp +++ b/hiro/windows/desktop.cpp @@ -1,13 +1,17 @@ -namespace phoenix { +#if defined(Hiro_Desktop) -Size pDesktop::size() { +namespace hiro { + +auto pDesktop::size() -> Size { return {GetSystemMetrics(SM_CXVIRTUALSCREEN), GetSystemMetrics(SM_CYVIRTUALSCREEN)}; } -Geometry pDesktop::workspace() { +auto pDesktop::workspace() -> Geometry { RECT rc; SystemParametersInfo(SPI_GETWORKAREA, 0, &rc, 0); return {rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top}; } } + +#endif diff --git a/hiro/windows/desktop.hpp b/hiro/windows/desktop.hpp new file mode 100644 index 00000000..2c179b61 --- /dev/null +++ b/hiro/windows/desktop.hpp @@ -0,0 +1,12 @@ +#if defined(Hiro_Desktop) + +namespace hiro { + +struct pDesktop { + static auto size() -> Size; + static auto workspace() -> Geometry; +}; + +} + +#endif diff --git a/hiro/windows/font.cpp b/hiro/windows/font.cpp index 6cfadfb2..f76baa79 100644 --- a/hiro/windows/font.cpp +++ b/hiro/windows/font.cpp @@ -1,34 +1,36 @@ -namespace phoenix { +#if defined(Hiro_Font) -string pFont::serif(unsigned size, string style) { +namespace hiro { + +auto pFont::serif(unsigned size, string style) -> string { if(size == 0) size = 8; if(style == "") style = "Normal"; return {"Georgia, ", size, ", ", style}; } -string pFont::sans(unsigned size, string style) { +auto pFont::sans(unsigned size, string style) -> string { if(size == 0) size = 8; if(style == "") style = "Normal"; return {"Tahoma, ", size, ", ", style}; } -string pFont::monospace(unsigned size, string style) { +auto pFont::monospace(unsigned size, string style) -> string { if(size == 0) size = 8; if(style == "") style = "Normal"; return {"Lucida Console, ", size, ", ", style}; } -Size pFont::size(string font, string text) { +auto pFont::size(const string& font, const string& text) -> Size { HFONT hfont = pFont::create(font); Size size = pFont::size(hfont, text); pFont::free(hfont); return size; } -HFONT pFont::create(string description) { +auto pFont::create(const string& description) -> HFONT { lstring part = description.split<2>(",").strip(); - string family = "Sans"; + string family = "Tahoma"; unsigned size = 8u; bool bold = false; bool italic = false; @@ -40,16 +42,16 @@ HFONT pFont::create(string description) { return CreateFont( -(size * 96.0 / 72.0 + 0.5), - 0, 0, 0, bold == false ? FW_NORMAL : FW_BOLD, italic, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, bold ? FW_BOLD : FW_NORMAL, italic, 0, 0, 0, 0, 0, 0, 0, utf16_t(family) ); } -void pFont::free(HFONT hfont) { +auto pFont::free(HFONT hfont) -> void { DeleteObject(hfont); } -Size pFont::size(HFONT hfont, string text) { +auto pFont::size(HFONT hfont, string text) -> Size { //temporary fix: empty text string returns height of zero; bad for eg Button height if(text.empty()) text = " "; @@ -62,3 +64,5 @@ Size pFont::size(HFONT hfont, string text) { } } + +#endif diff --git a/hiro/windows/font.hpp b/hiro/windows/font.hpp new file mode 100644 index 00000000..c899e31f --- /dev/null +++ b/hiro/windows/font.hpp @@ -0,0 +1,18 @@ +#if defined(Hiro_Font) + +namespace hiro { + +struct pFont { + static auto serif(unsigned size, string style) -> string; + static auto sans(unsigned size, string style) -> string; + static auto monospace(unsigned size, string style) -> string; + static auto size(const string& font, const string& text) -> Size; + + static auto create(const string& description) -> HFONT; + static auto free(HFONT hfont) -> void; + static auto size(HFONT hfont, string text) -> Size; +}; + +} + +#endif diff --git a/hiro/windows/group.cpp b/hiro/windows/group.cpp new file mode 100644 index 00000000..cbf387ff --- /dev/null +++ b/hiro/windows/group.cpp @@ -0,0 +1,13 @@ +#if defined(Hiro_Group) + +namespace hiro { + +auto pGroup::construct() -> void { +} + +auto pGroup::destruct() -> void { +} + +} + +#endif diff --git a/hiro/windows/group.hpp b/hiro/windows/group.hpp new file mode 100644 index 00000000..a2ac6d72 --- /dev/null +++ b/hiro/windows/group.hpp @@ -0,0 +1,11 @@ +#if defined(Hiro_Group) + +namespace hiro { + +struct pGroup : pObject { + Declare(Group, Object) +}; + +} + +#endif diff --git a/hiro/windows/header.hpp b/hiro/windows/header.hpp index d50ce963..6314c8eb 100644 --- a/hiro/windows/header.hpp +++ b/hiro/windows/header.hpp @@ -15,4 +15,42 @@ #include #include -#define TBS_TRANSPARENTBKGND 0x1000 +//MinGW/32-bit has painfully outdated platform headers ... + +#if !defined(Button_SetImageList) + typedef struct { + HIMAGELIST himl; + RECT margin; + UINT uAlign; + } BUTTON_IMAGELIST, *PBUTTON_IMAGELIST; + + #define BUTTON_IMAGELIST_ALIGN_LEFT 0 + #define BUTTON_IMAGELIST_ALIGN_RIGHT 1 + #define BUTTON_IMAGELIST_ALIGN_TOP 2 + #define BUTTON_IMAGELIST_ALIGN_BOTTOM 3 + #define BUTTON_IMAGELIST_ALIGN_CENTER 4 + + #define BCM_FIRST 0x1600 + #define BCM_SETIMAGELIST (BCM_FIRST+2) + #define Button_SetImageList(hwnd, pbuttonImagelist) (WINBOOL)SNDMSG((hwnd),BCM_SETIMAGELIST,0,(LPARAM)(pbuttonImagelist)) +#endif + +#if !defined(BP_CHECKBOX) + #define BP_CHECKBOX 3 +#endif + +#if !defined(CBS_UNCHECKEDNORMAL) + #define CBS_UNCHECKEDNORMAL 1 +#endif + +#if !defined(CBS_CHECKEDNORMAL) + #define CBS_CHECKEDNORMAL 5 +#endif + +#if !defined(LVCFMT_FIXED_WIDTH) + #define LVCFMT_FIXED_WIDTH 0x0100 +#endif + +#if !defined(TBS_TRANSPARENTBKGND) + #define TBS_TRANSPARENTBKGND 0x1000 +#endif diff --git a/hiro/windows/phoenix.Manifest b/hiro/windows/hiro.Manifest similarity index 87% rename from hiro/windows/phoenix.Manifest rename to hiro/windows/hiro.Manifest index 45fbb4cd..a7e39c8d 100644 --- a/hiro/windows/phoenix.Manifest +++ b/hiro/windows/hiro.Manifest @@ -1,6 +1,6 @@ - + diff --git a/hiro/windows/hiro.rc b/hiro/windows/hiro.rc new file mode 100644 index 00000000..25c6e7dd --- /dev/null +++ b/hiro/windows/hiro.rc @@ -0,0 +1 @@ +1 24 "hiro.Manifest" diff --git a/hiro/windows/hotkey.cpp b/hiro/windows/hotkey.cpp new file mode 100644 index 00000000..608ba7bb --- /dev/null +++ b/hiro/windows/hotkey.cpp @@ -0,0 +1,13 @@ +#if defined(Hiro_Hotkey) + +namespace hiro { + +auto pHotkey::construct() -> void { +} + +auto pHotkey::destruct() -> void { +} + +} + +#endif diff --git a/hiro/windows/hotkey.hpp b/hiro/windows/hotkey.hpp new file mode 100644 index 00000000..b1caa7bc --- /dev/null +++ b/hiro/windows/hotkey.hpp @@ -0,0 +1,11 @@ +#if defined(Hiro_Hotkey) + +namespace hiro { + +struct pHotkey : pObject { + Declare(Hotkey, Object) +}; + +} + +#endif diff --git a/hiro/windows/keyboard.cpp b/hiro/windows/keyboard.cpp index 5b1f4213..c269ba37 100644 --- a/hiro/windows/keyboard.cpp +++ b/hiro/windows/keyboard.cpp @@ -1,144 +1,99 @@ -namespace phoenix { +#if defined(Hiro_Keyboard) -void pKeyboard::initialize() { - auto append = [](Keyboard::Scancode scancode, unsigned keysym) { - settings->keymap.insert(scancode, keysym); - }; +namespace hiro { - append(Keyboard::Scancode::Escape, VK_ESCAPE); - append(Keyboard::Scancode::F1, VK_F1); - append(Keyboard::Scancode::F2, VK_F2); - append(Keyboard::Scancode::F3, VK_F3); - append(Keyboard::Scancode::F4, VK_F4); - append(Keyboard::Scancode::F5, VK_F5); - append(Keyboard::Scancode::F6, VK_F6); - append(Keyboard::Scancode::F7, VK_F7); - append(Keyboard::Scancode::F8, VK_F8); - append(Keyboard::Scancode::F9, VK_F9); - append(Keyboard::Scancode::F10, VK_F10); - append(Keyboard::Scancode::F11, VK_F11); - append(Keyboard::Scancode::F12, VK_F12); +vector pKeyboard::keycodes; - append(Keyboard::Scancode::PrintScreen, VK_SNAPSHOT); - append(Keyboard::Scancode::ScrollLock, VK_SCROLL); - append(Keyboard::Scancode::Pause, VK_PAUSE); - - append(Keyboard::Scancode::Insert, VK_INSERT); - append(Keyboard::Scancode::Delete, VK_DELETE); - append(Keyboard::Scancode::Home, VK_HOME); - append(Keyboard::Scancode::End, VK_END); - append(Keyboard::Scancode::PageUp, VK_PRIOR); - append(Keyboard::Scancode::PageDown, VK_NEXT); - - append(Keyboard::Scancode::Up, VK_UP); - append(Keyboard::Scancode::Down, VK_DOWN); - append(Keyboard::Scancode::Left, VK_LEFT); - append(Keyboard::Scancode::Right, VK_RIGHT); - - append(Keyboard::Scancode::Grave, VK_OEM_3); - append(Keyboard::Scancode::Number1, '1'); - append(Keyboard::Scancode::Number2, '2'); - append(Keyboard::Scancode::Number3, '3'); - append(Keyboard::Scancode::Number4, '4'); - append(Keyboard::Scancode::Number5, '5'); - append(Keyboard::Scancode::Number6, '6'); - append(Keyboard::Scancode::Number7, '7'); - append(Keyboard::Scancode::Number8, '8'); - append(Keyboard::Scancode::Number9, '9'); - append(Keyboard::Scancode::Number0, '0'); - append(Keyboard::Scancode::Minus, VK_OEM_MINUS); - append(Keyboard::Scancode::Equal, VK_OEM_PLUS); - append(Keyboard::Scancode::Backspace, VK_BACK); - - append(Keyboard::Scancode::BracketLeft, VK_OEM_4); - append(Keyboard::Scancode::BracketRight, VK_OEM_6); - append(Keyboard::Scancode::Backslash, VK_OEM_5); - append(Keyboard::Scancode::Semicolon, VK_OEM_1); - append(Keyboard::Scancode::Apostrophe, VK_OEM_7); - append(Keyboard::Scancode::Comma, VK_OEM_COMMA); - append(Keyboard::Scancode::Period, VK_OEM_PERIOD); - append(Keyboard::Scancode::Slash, VK_OEM_2); - - append(Keyboard::Scancode::Tab, VK_TAB); - append(Keyboard::Scancode::CapsLock, VK_CAPITAL); - append(Keyboard::Scancode::Return, VK_RETURN); - append(Keyboard::Scancode::ShiftLeft, VK_LSHIFT); - append(Keyboard::Scancode::ShiftRight, VK_RSHIFT); - append(Keyboard::Scancode::ControlLeft, VK_LCONTROL); - append(Keyboard::Scancode::ControlRight, VK_RCONTROL); - append(Keyboard::Scancode::SuperLeft, VK_LWIN); - append(Keyboard::Scancode::SuperRight, VK_RWIN); - append(Keyboard::Scancode::AltLeft, VK_LMENU); - append(Keyboard::Scancode::AltRight, VK_RMENU); - append(Keyboard::Scancode::Space, VK_SPACE); - append(Keyboard::Scancode::Menu, VK_APPS); - - append(Keyboard::Scancode::A, 'A'); - append(Keyboard::Scancode::B, 'B'); - append(Keyboard::Scancode::C, 'C'); - append(Keyboard::Scancode::D, 'D'); - append(Keyboard::Scancode::E, 'E'); - append(Keyboard::Scancode::F, 'F'); - append(Keyboard::Scancode::G, 'G'); - append(Keyboard::Scancode::H, 'H'); - append(Keyboard::Scancode::I, 'I'); - append(Keyboard::Scancode::J, 'J'); - append(Keyboard::Scancode::K, 'K'); - append(Keyboard::Scancode::L, 'L'); - append(Keyboard::Scancode::M, 'M'); - append(Keyboard::Scancode::N, 'N'); - append(Keyboard::Scancode::O, 'O'); - append(Keyboard::Scancode::P, 'P'); - append(Keyboard::Scancode::Q, 'Q'); - append(Keyboard::Scancode::R, 'R'); - append(Keyboard::Scancode::S, 'S'); - append(Keyboard::Scancode::T, 'T'); - append(Keyboard::Scancode::U, 'U'); - append(Keyboard::Scancode::V, 'V'); - append(Keyboard::Scancode::W, 'W'); - append(Keyboard::Scancode::X, 'X'); - append(Keyboard::Scancode::Y, 'Y'); - append(Keyboard::Scancode::Z, 'Z'); - - append(Keyboard::Scancode::NumLock, VK_NUMLOCK); - append(Keyboard::Scancode::Divide, VK_DIVIDE); - append(Keyboard::Scancode::Multiply, VK_MULTIPLY); - append(Keyboard::Scancode::Subtract, VK_SUBTRACT); - append(Keyboard::Scancode::Add, VK_ADD); -//append(Keyboard::Scancode::Enter, ...); - append(Keyboard::Scancode::Point, VK_DECIMAL); - - append(Keyboard::Scancode::Keypad1, VK_NUMPAD1); - append(Keyboard::Scancode::Keypad2, VK_NUMPAD2); - append(Keyboard::Scancode::Keypad3, VK_NUMPAD3); - append(Keyboard::Scancode::Keypad4, VK_NUMPAD4); - append(Keyboard::Scancode::Keypad5, VK_NUMPAD5); - append(Keyboard::Scancode::Keypad6, VK_NUMPAD6); - append(Keyboard::Scancode::Keypad7, VK_NUMPAD7); - append(Keyboard::Scancode::Keypad8, VK_NUMPAD8); - append(Keyboard::Scancode::Keypad9, VK_NUMPAD9); - append(Keyboard::Scancode::Keypad0, VK_NUMPAD0); +auto pKeyboard::poll() -> vector { + vector result; + for(auto& code : keycodes) result.append(pressed(code)); + return result; } -bool pKeyboard::pressed(Keyboard::Scancode scancode) { - if(auto result = settings->keymap.find(scancode)) { - return GetAsyncKeyState(result()) & 0x8000; - } +auto pKeyboard::pressed(unsigned code) -> bool { + uint8_t lo = code >> 0; + uint8_t hi = code >> 8; + if(lo && GetAsyncKeyState(lo) & 0x8000) return true; + if(hi && GetAsyncKeyState(hi) & 0x8000) return true; return false; } -vector pKeyboard::state() { - vector output; - output.resize((unsigned)Keyboard::Scancode::Limit); - for(auto& n : output) n = false; +auto pKeyboard::initialize() -> void { + auto append = [](unsigned lo, unsigned hi = 0) { + keycodes.append(lo << 0 | hi << 8); + }; - for(auto node : settings->keymap) { - if(GetAsyncKeyState(node.value) & 0x8000) { - output[(unsigned)node.key] = true; - } + #define map(name, ...) if(key == name) { append(__VA_ARGS__); continue; } + for(auto& key : Keyboard::keys) { + #include + //print("[hiro/windows] warning: unhandled key: ", key, "\n"); + append(0); + } + #undef map +} + +auto pKeyboard::_translate(unsigned code, unsigned flags) -> signed { + bool numLock = GetKeyState(VK_NUMLOCK); + bool capsLock = GetKeyState(VK_CAPITAL); + bool shifted = (GetAsyncKeyState(VK_LSHIFT) & 0x8000) || (GetAsyncKeyState(VK_RSHIFT) & 0x8000); + bool pressed = GetAsyncKeyState(code) & 0x8000; + bool extended = flags & (1 << 24); + + switch(code) { + case VK_OEM_3: return !shifted ? '`' : '~'; + case '1': return !shifted ? '1' : '!'; + case '2': return !shifted ? '2' : '@'; + case '3': return !shifted ? '3' : '#'; + case '4': return !shifted ? '4' : '$'; + case '5': return !shifted ? '5' : '%'; + case '6': return !shifted ? '6' : '^'; + case '7': return !shifted ? '7' : '&'; + case '8': return !shifted ? '8' : '*'; + case '9': return !shifted ? '9' : '('; + case '0': return !shifted ? '0' : ')'; + case VK_OEM_MINUS: return !shifted ? '-' : '_'; + case VK_OEM_PLUS: return !shifted ? '=' : '+'; + case VK_BACK: return '\b'; + + case VK_TAB: return '\t'; + case VK_RETURN: return '\n'; + case VK_SPACE: return ' '; + + case VK_OEM_4: return !shifted ? '[' : '{'; + case VK_OEM_6: return !shifted ? ']' : '}'; + case VK_OEM_5: return !shifted ? '\\' : '|'; + case VK_OEM_1: return !shifted ? ';' : ':'; + case VK_OEM_7: return !shifted ? '\'' : '\"'; + case VK_OEM_COMMA: return !shifted ? ',' : '<'; + case VK_OEM_PERIOD: return !shifted ? '.' : '>'; + case VK_OEM_2: return !shifted ? '/' : '?'; + + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': + case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': + if(capsLock) return !shifted ? code : code + 32; + else return !shifted ? code + 32 : code; + + case VK_DIVIDE: return '/'; + case VK_MULTIPLY: return '*'; + case VK_SUBTRACT: return '-'; + case VK_ADD: return '+'; + case VK_DECIMAL: return '.'; + + case VK_NUMPAD1: return numLock ? '1' : 0; + case VK_NUMPAD2: return numLock ? '2' : 0; + case VK_NUMPAD3: return numLock ? '3' : 0; + case VK_NUMPAD4: return numLock ? '4' : 0; + case VK_NUMPAD5: return numLock ? '5' : 0; + case VK_NUMPAD6: return numLock ? '6' : 0; + case VK_NUMPAD7: return numLock ? '7' : 0; + case VK_NUMPAD8: return numLock ? '8' : 0; + case VK_NUMPAD9: return numLock ? '9' : 0; + case VK_NUMPAD0: return numLock ? '0' : 0; } - return output; + return 0; } } + +#endif diff --git a/hiro/windows/keyboard.hpp b/hiro/windows/keyboard.hpp new file mode 100644 index 00000000..5cf00c82 --- /dev/null +++ b/hiro/windows/keyboard.hpp @@ -0,0 +1,18 @@ +#if defined(Hiro_Keyboard) + +namespace hiro { + +struct pKeyboard { + static auto poll() -> vector; + static auto pressed(unsigned code) -> bool; + + static auto initialize() -> void; + + static auto _translate(unsigned code, unsigned flags) -> signed; + + static vector keycodes; +}; + +} + +#endif diff --git a/hiro/windows/layout.cpp b/hiro/windows/layout.cpp new file mode 100644 index 00000000..dfa22c83 --- /dev/null +++ b/hiro/windows/layout.cpp @@ -0,0 +1,22 @@ +#if defined(Hiro_Layout) + +namespace hiro { + +auto pLayout::construct() -> void { +} + +auto pLayout::destruct() -> void { +} + +auto pLayout::setEnabled(bool enabled) -> void { +} + +auto pLayout::setFont(const string& font) -> void { +} + +auto pLayout::setVisible(bool visible) -> void { +} + +} + +#endif diff --git a/hiro/windows/layout.hpp b/hiro/windows/layout.hpp new file mode 100644 index 00000000..3895710c --- /dev/null +++ b/hiro/windows/layout.hpp @@ -0,0 +1,15 @@ +#if defined(Hiro_Layout) + +namespace hiro { + +struct pLayout : pSizable { + Declare(Layout, Sizable) + + auto setEnabled(bool enabled) -> void override; + auto setFont(const string& font) -> void override; + auto setVisible(bool visible) -> void override; +}; + +} + +#endif diff --git a/hiro/windows/menu-bar.cpp b/hiro/windows/menu-bar.cpp new file mode 100644 index 00000000..bc98b95c --- /dev/null +++ b/hiro/windows/menu-bar.cpp @@ -0,0 +1,83 @@ +#if defined(Hiro_MenuBar) + +namespace hiro { + +auto pMenuBar::construct() -> void { + _update(); +} + +auto pMenuBar::destruct() -> void { + if(hmenu) { DestroyMenu(hmenu); hmenu = nullptr; } + if(auto parent = _parent()) { + SetMenu(parent->hwnd, nullptr); + } +} + +auto pMenuBar::append(sMenu) -> void { + _update(); +} + +auto pMenuBar::remove(sMenu) -> void { + _update(); +} + +auto pMenuBar::setEnabled(bool enabled) -> void { + _update(); +} + +auto pMenuBar::setFont(const string& font) -> void { + //unsupported +} + +auto pMenuBar::setVisible(bool visible) -> void { + if(auto parent = _parent()) { + SetMenu(parent->hwnd, visible ? hmenu : nullptr); + parent->setGeometry(parent->state().geometry); + } +} + +auto pMenuBar::_parent() -> maybe { + if(auto parent = self().parentWindow(true)) { + if(auto self = parent->self()) return *self; + } + return nothing; +} + +auto pMenuBar::_update() -> void { + if(hmenu) DestroyMenu(hmenu); + hmenu = CreateMenu(); + + MENUINFO mi{sizeof(MENUINFO)}; + mi.fMask = MIM_STYLE; + mi.dwStyle = MNS_NOTIFYBYPOS; //| MNS_MODELESS; + SetMenuInfo(hmenu, &mi); + + unsigned position = 0; + + #if defined(Hiro_Menu) + for(auto& menu : state().menus) { + unsigned enabled = menu->enabled() ? 0 : MF_GRAYED; + + MENUITEMINFO mii{sizeof(MENUITEMINFO)}; + mii.fMask = MIIM_DATA; + mii.dwItemData = (ULONG_PTR)menu.data(); + + if(menu->visible()) { + if(auto self = menu->self()) { + self->_update(); + AppendMenu(hmenu, MF_STRING | MF_POPUP | enabled, (UINT_PTR)self->hmenu, utf16_t(menu->text())); + SetMenuItemInfo(hmenu, position++, true, &mii); + } + } + } + #endif + + if(auto parent = _parent()) { + SetMenu(parent->hwnd, self().visible(true) ? hmenu : nullptr); + parent->setGeometry(parent->state().geometry); + } +} + +} + +#endif diff --git a/hiro/windows/menu-bar.hpp b/hiro/windows/menu-bar.hpp new file mode 100644 index 00000000..13d4b2af --- /dev/null +++ b/hiro/windows/menu-bar.hpp @@ -0,0 +1,23 @@ +#if defined(Hiro_MenuBar) + +namespace hiro { + +struct pMenuBar : pObject { + Declare(MenuBar, Object) + + auto append(sMenu menu) -> void; + auto remove(sMenu menu) -> void; + auto setEnabled(bool enabled) -> void override; + auto setFont(const string& font) -> void override; + auto setVisible(bool visible) -> void override; + + auto _parent() -> maybe; + auto _update() -> void; + + HMENU hmenu = 0; + vector objects; +}; + +} + +#endif diff --git a/hiro/windows/message-window.cpp b/hiro/windows/message-window.cpp index 6aa8c4f4..cee1f36d 100644 --- a/hiro/windows/message-window.cpp +++ b/hiro/windows/message-window.cpp @@ -1,6 +1,8 @@ -namespace phoenix { +#if defined(Hiro_MessageWindow) -static MessageWindow::Response MessageWindow_response(MessageWindow::Buttons buttons, UINT response) { +namespace hiro { + +static auto MessageWindow_response(MessageWindow::Buttons buttons, UINT response) -> MessageWindow::Response { if(response == IDOK) return MessageWindow::Response::Ok; if(response == IDCANCEL) return MessageWindow::Response::Cancel; if(response == IDYES) return MessageWindow::Response::Yes; @@ -15,7 +17,7 @@ static MessageWindow::Response MessageWindow_response(MessageWindow::Buttons but throw; } -static UINT MessageWindow_buttons(MessageWindow::Buttons buttons) { +static auto MessageWindow_buttons(MessageWindow::Buttons buttons) -> UINT { if(buttons == MessageWindow::Buttons::Ok) return MB_OK; if(buttons == MessageWindow::Buttons::OkCancel) return MB_OKCANCEL; if(buttons == MessageWindow::Buttons::YesNo) return MB_YESNO; @@ -23,32 +25,34 @@ static UINT MessageWindow_buttons(MessageWindow::Buttons buttons) { throw; } -MessageWindow::Response pMessageWindow::error(MessageWindow::State& state) { +auto pMessageWindow::error(MessageWindow::State& state) -> MessageWindow::Response { UINT flags = MB_ICONERROR | MessageWindow_buttons(state.buttons); return MessageWindow_response(state.buttons, MessageBox( - state.parent ? state.parent->p.hwnd : 0, utf16_t(state.text), utf16_t(state.title), flags + state.parent ? state.parent->self()->hwnd : 0, utf16_t(state.text), utf16_t(state.title), flags )); } -MessageWindow::Response pMessageWindow::information(MessageWindow::State& state) { +auto pMessageWindow::information(MessageWindow::State& state) -> MessageWindow::Response { UINT flags = MB_ICONINFORMATION | MessageWindow_buttons(state.buttons); return MessageWindow_response(state.buttons, MessageBox( - state.parent ? state.parent->p.hwnd : 0, utf16_t(state.text), utf16_t(state.title), flags + state.parent ? state.parent->self()->hwnd : 0, utf16_t(state.text), utf16_t(state.title), flags )); } -MessageWindow::Response pMessageWindow::question(MessageWindow::State& state) { +auto pMessageWindow::question(MessageWindow::State& state) -> MessageWindow::Response { UINT flags = MB_ICONQUESTION | MessageWindow_buttons(state.buttons); return MessageWindow_response(state.buttons, MessageBox( - state.parent ? state.parent->p.hwnd : 0, utf16_t(state.text), utf16_t(state.title), flags + state.parent ? state.parent->self()->hwnd : 0, utf16_t(state.text), utf16_t(state.title), flags )); } -MessageWindow::Response pMessageWindow::warning(MessageWindow::State& state) { +auto pMessageWindow::warning(MessageWindow::State& state) -> MessageWindow::Response { UINT flags = MB_ICONWARNING | MessageWindow_buttons(state.buttons); return MessageWindow_response(state.buttons, MessageBox( - state.parent ? state.parent->p.hwnd : 0, utf16_t(state.text), utf16_t(state.title), flags + state.parent ? state.parent->self()->hwnd : 0, utf16_t(state.text), utf16_t(state.title), flags )); } } + +#endif diff --git a/hiro/windows/message-window.hpp b/hiro/windows/message-window.hpp new file mode 100644 index 00000000..b8b26af5 --- /dev/null +++ b/hiro/windows/message-window.hpp @@ -0,0 +1,14 @@ +#if defined(Hiro_MessageWindow) + +namespace hiro { + +struct pMessageWindow { + static auto error(MessageWindow::State& state) -> MessageWindow::Response; + static auto information(MessageWindow::State& state) -> MessageWindow::Response; + static auto question(MessageWindow::State& state) -> MessageWindow::Response; + static auto warning(MessageWindow::State& state) -> MessageWindow::Response; +}; + +} + +#endif diff --git a/hiro/windows/monitor.cpp b/hiro/windows/monitor.cpp index beb4b5c3..84a90c53 100644 --- a/hiro/windows/monitor.cpp +++ b/hiro/windows/monitor.cpp @@ -1,4 +1,6 @@ -namespace phoenix { +#if defined(Hiro_Monitor) + +namespace hiro { struct MonitorInfo { unsigned monitor = 0; @@ -7,7 +9,7 @@ struct MonitorInfo { Geometry geometry; }; -static BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { +static auto CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) -> BOOL { MonitorInfo& info = *(MonitorInfo*)dwData; MONITORINFOEX mi; memset(&mi, 0, sizeof(MONITORINFOEX)); @@ -23,21 +25,23 @@ static BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT l return TRUE; } -unsigned pMonitor::count() { +auto pMonitor::count() -> unsigned { return GetSystemMetrics(SM_CMONITORS); } -Geometry pMonitor::geometry(unsigned monitor) { +auto pMonitor::geometry(unsigned monitor) -> Geometry { MonitorInfo info; info.monitor = monitor; EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, (LPARAM)&info); return info.geometry; } -unsigned pMonitor::primary() { +auto pMonitor::primary() -> unsigned { MonitorInfo info; EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, (LPARAM)&info); return info.primary; } } + +#endif diff --git a/hiro/windows/monitor.hpp b/hiro/windows/monitor.hpp new file mode 100644 index 00000000..2ec76265 --- /dev/null +++ b/hiro/windows/monitor.hpp @@ -0,0 +1,13 @@ +#if defined(Hiro_Monitor) + +namespace hiro { + +struct pMonitor { + static auto count() -> unsigned; + static auto geometry(unsigned monitor) -> Geometry; + static auto primary() -> unsigned; +}; + +} + +#endif diff --git a/hiro/windows/mouse.cpp b/hiro/windows/mouse.cpp index ee3a0f06..89898ad8 100644 --- a/hiro/windows/mouse.cpp +++ b/hiro/windows/mouse.cpp @@ -1,12 +1,14 @@ -namespace phoenix { +#if defined(Hiro_Mouse) -Position pMouse::position() { - POINT point = {0}; +namespace hiro { + +auto pMouse::position() -> Position { + POINT point{0}; GetCursorPos(&point); return {point.x, point.y}; } -bool pMouse::pressed(Mouse::Button button) { +auto pMouse::pressed(Mouse::Button button) -> bool { switch(button) { case Mouse::Button::Left: return GetAsyncKeyState(VK_LBUTTON) & 0x8000; case Mouse::Button::Middle: return GetAsyncKeyState(VK_MBUTTON) & 0x8000; @@ -16,3 +18,5 @@ bool pMouse::pressed(Mouse::Button button) { } } + +#endif diff --git a/hiro/windows/mouse.hpp b/hiro/windows/mouse.hpp new file mode 100644 index 00000000..72a56058 --- /dev/null +++ b/hiro/windows/mouse.hpp @@ -0,0 +1,12 @@ +#if defined(Hiro_Mouse) + +namespace hiro { + +struct pMouse { + static auto position() -> Position; + static auto pressed(Mouse::Button button) -> bool; +}; + +} + +#endif diff --git a/hiro/windows/object.cpp b/hiro/windows/object.cpp index fc30791f..bc074c4a 100644 --- a/hiro/windows/object.cpp +++ b/hiro/windows/object.cpp @@ -1,17 +1,41 @@ -namespace phoenix { +#if defined(Hiro_Object) -vector pObject::objects; +namespace hiro { -pObject::pObject(Object& object) : object(object) { - static unsigned uniqueId = 100; - objects.append(this); - id = uniqueId++; - locked = false; +auto pObject::construct() -> void { } -Object* pObject::find(unsigned id) { - for(auto& item : objects) if(item->id == id) return &item->object; - return nullptr; +auto pObject::destruct() -> void { +} + +auto pObject::reconstruct() -> void { +} + +auto pObject::focused() const -> bool { + return false; +} + +auto pObject::remove() -> void { +} + +auto pObject::reset() -> void { +} + +auto pObject::setEnabled(bool enabled) -> void { +} + +auto pObject::setFocused() -> void { +} + +auto pObject::setFont(const string& font) -> void { +} + +auto pObject::setGroup(sGroup group) -> void { +} + +auto pObject::setVisible(bool visible) -> void { } } + +#endif diff --git a/hiro/windows/object.hpp b/hiro/windows/object.hpp new file mode 100644 index 00000000..4951650e --- /dev/null +++ b/hiro/windows/object.hpp @@ -0,0 +1,31 @@ +#if defined(Hiro_Object) + +namespace hiro { + +struct pObject { + pObject(mObject& reference) : reference(reference) {} + virtual ~pObject() = default; + virtual auto construct() -> void; + virtual auto destruct() -> void; + virtual auto reconstruct() -> void; + + virtual auto focused() const -> bool; + virtual auto remove() -> void; + virtual auto reset() -> void; + virtual auto setEnabled(bool enabled) -> void; + virtual auto setFocused() -> void; + virtual auto setFont(const string& font) -> void; + virtual auto setGroup(sGroup group) -> void; + virtual auto setVisible(bool visible) -> void; + + auto locked() const -> bool { return locks != 0; } + auto lock() -> void { ++locks; } + auto unlock() -> void { --locks; } + + mObject& reference; + signed locks = 0; +}; + +} + +#endif diff --git a/hiro/windows/phoenix.rc b/hiro/windows/phoenix.rc deleted file mode 100644 index 89fb8dc2..00000000 --- a/hiro/windows/phoenix.rc +++ /dev/null @@ -1 +0,0 @@ -1 24 "phoenix.Manifest" diff --git a/hiro/windows/platform.cpp b/hiro/windows/platform.cpp index 91bff19d..c32c77f6 100644 --- a/hiro/windows/platform.cpp +++ b/hiro/windows/platform.cpp @@ -1,7 +1,5 @@ #include "platform.hpp" - #include "utility.cpp" -#include "settings.cpp" #include "desktop.cpp" #include "monitor.cpp" @@ -9,17 +7,27 @@ #include "mouse.cpp" #include "browser-window.cpp" #include "message-window.cpp" + #include "object.cpp" +#include "group.cpp" + +#include "hotkey.cpp" #include "font.cpp" #include "timer.cpp" #include "window.cpp" +#include "status-bar.cpp" +#include "menu-bar.cpp" +#include "popup-menu.cpp" #include "action/action.cpp" #include "action/menu.cpp" -#include "action/separator.cpp" -#include "action/item.cpp" -#include "action/check-item.cpp" -#include "action/radio-item.cpp" +#include "action/menu-separator.cpp" +#include "action/menu-item.cpp" +#include "action/menu-check-item.cpp" +#include "action/menu-radio-item.cpp" + +#include "sizable.cpp" +#include "layout.cpp" #include "widget/widget.cpp" #include "widget/button.cpp" @@ -27,7 +35,7 @@ #include "widget/check-button.cpp" #include "widget/check-label.cpp" #include "widget/combo-button.cpp" -#include "widget/console.cpp" +#include "widget/combo-button-item.cpp" #include "widget/frame.cpp" #include "widget/hex-edit.cpp" #include "widget/horizontal-scroller.cpp" @@ -35,10 +43,14 @@ #include "widget/label.cpp" #include "widget/line-edit.cpp" #include "widget/list-view.cpp" +#include "widget/list-view-column.cpp" +#include "widget/list-view-item.cpp" +#include "widget/list-view-cell.cpp" #include "widget/progress-bar.cpp" #include "widget/radio-button.cpp" #include "widget/radio-label.cpp" #include "widget/tab-frame.cpp" +#include "widget/tab-frame-item.cpp" #include "widget/text-edit.cpp" #include "widget/vertical-scroller.cpp" #include "widget/vertical-slider.cpp" diff --git a/hiro/windows/platform.hpp b/hiro/windows/platform.hpp index 36366c6b..4cbd5649 100644 --- a/hiro/windows/platform.hpp +++ b/hiro/windows/platform.hpp @@ -1,25 +1,4 @@ -namespace phoenix { - -struct AppMessage { - enum : unsigned { - ListView_onActivate = 0, - }; -}; - -typedef LRESULT CALLBACK (*WindowProc)(HWND, UINT, WPARAM, LPARAM); - -struct pApplication { - static void run(); - static bool pendingEvents(); - static void processEvents(); - static void quit(); - - static void initialize(); -}; - -struct Settings { - bimap keymap; -}; +namespace hiro { struct pFont; struct pObject; @@ -28,620 +7,81 @@ struct pMenu; struct pLayout; struct pWidget; -struct pFont { - static string serif(unsigned size, string style); - static string sans(unsigned size, string style); - static string monospace(unsigned size, string style); - static Size size(string font, string text); - - static HFONT create(string description); - static void free(HFONT hfont); - static Size size(HFONT hfont, string text); +struct AppMessage { + enum : unsigned { + None = WM_APP, + ListView_onActivate, + ListView_onChange, + }; }; -struct pDesktop { - static Size size(); - static Geometry workspace(); -}; - -struct pMonitor { - static unsigned count(); - static Geometry geometry(unsigned monitor); - static unsigned primary(); -}; - -struct pKeyboard { - static bool pressed(Keyboard::Scancode scancode); - static vector state(); - - static void initialize(); -}; - -struct pMouse { - static Position position(); - static bool pressed(Mouse::Button button); -}; - -struct pBrowserWindow { - static string directory(BrowserWindow::State& state); - static string open(BrowserWindow::State& state); - static string save(BrowserWindow::State& state); -}; - -struct pMessageWindow { - static MessageWindow::Response error(MessageWindow::State& state); - static MessageWindow::Response information(MessageWindow::State& state); - static MessageWindow::Response question(MessageWindow::State& state); - static MessageWindow::Response warning(MessageWindow::State& state); -}; - -struct pObject { - static vector objects; - - Object& object; - uintptr_t id; - bool locked; - - pObject(Object& object); - static Object* find(unsigned id); - virtual ~pObject() {} - - void constructor() {} - void destructor() {} -}; - -struct pTimer : public pObject { - Timer& timer; - UINT_PTR htimer; - - void setEnabled(bool enabled); - void setInterval(unsigned interval); - - pTimer(Timer& timer) : pObject(timer), timer(timer) {} - void constructor(); -}; - -struct pWindow : public pObject { - static vector modal; - static void updateModality(); - - Window& window; - HWND hwnd; - HMENU hmenu; - HWND hstatus; - HFONT hstatusfont; - HBRUSH brush; - COLORREF brushColor; - - static Window& none(); - - void append(Layout& layout); - void append(Menu& menu); - void append(Widget& widget); - bool focused(); - Geometry frameMargin(); - Geometry geometry(); - void remove(Layout& layout); - void remove(Menu& menu); - void remove(Widget& widget); - void setBackgroundColor(Color color); - void setDroppable(bool droppable); - void setFocused(); - void setFullScreen(bool fullScreen); - void setGeometry(Geometry geometry); - void setMenuFont(string font); - void setMenuVisible(bool visible); - void setModal(bool modal); - void setResizable(bool resizable); - void setStatusFont(string font); - void setStatusText(string text); - void setStatusVisible(bool visible); - void setTitle(string text); - void setVisible(bool visible); - void setWidgetFont(string font); - - pWindow(Window& window) : pObject(window), window(window) {} - void constructor(); - void destructor(); - void updateMenu(); - - void onClose(); - void onDrop(WPARAM wparam); - bool onEraseBackground(); - void onModalBegin(); - void onModalEnd(); - void onMove(); - void onSize(); -}; - -struct pAction : public pObject { - Action& action; - Menu* parentMenu; - Window* parentWindow; - - void setEnabled(bool enabled); - void setVisible(bool visible); - - pAction(Action& action) : pObject(action), action(action) {} - void constructor(); -}; - -struct pMenu : public pAction { - Menu& menu; - HMENU hmenu; - HBITMAP hbitmap; - - void append(Action& action); - void remove(Action& action); - void setImage(const image& image); - void setText(string text); - - pMenu(Menu& menu) : pAction(menu), menu(menu), hbitmap(0) {} - void constructor(); - void destructor(); - void createBitmap(); - void update(Window& parentWindow, Menu* parentMenu = nullptr); -}; - -struct pSeparator : public pAction { - Separator& separator; - - pSeparator(Separator& separator) : pAction(separator), separator(separator) {} - void constructor(); - void destructor(); -}; - -struct pItem : public pAction { - Item& item; - HBITMAP hbitmap; - - void setImage(const image& image); - void setText(string text); - - pItem(Item& item) : pAction(item), item(item), hbitmap(0) {} - void constructor(); - void destructor(); - void createBitmap(); - - void onActivate(); -}; - -struct pCheckItem : public pAction { - CheckItem& checkItem; - - void setChecked(bool checked); - void setText(string text); - - pCheckItem(CheckItem& checkItem) : pAction(checkItem), checkItem(checkItem) {} - void constructor(); - void destructor(); - - void onToggle(); -}; - -struct pRadioItem : public pAction { - RadioItem& radioItem; - - void setChecked(); - void setGroup(const group& group); - void setText(string text); - - pRadioItem(RadioItem& radioItem) : pAction(radioItem), radioItem(radioItem) {} - void constructor(); - void destructor(); - - void onActivate(); -}; - -struct pSizable : public pObject { - Sizable& sizable; - - pSizable(Sizable& sizable) : pObject(sizable), sizable(sizable) {} -}; - -struct pLayout : public pSizable { - Layout& layout; - - pLayout(Layout& layout) : pSizable(layout), layout(layout) {} -}; - -struct pWidget : public pSizable { - Widget& widget; - HWND parentHwnd; - HWND hwnd; - HFONT hfont; - - bool focused(); - virtual Size minimumSize(); - virtual void setEnabled(bool enabled); - void setFocused(); - void setFont(string font); - virtual void setGeometry(Geometry geometry); - virtual void setVisible(bool visible); - - pWidget(Widget& widget) : pSizable(widget), widget(widget) { parentHwnd = pWindow::none().p.hwnd; } - void constructor(); - void destructor(); - virtual void orphan(); - void setDefaultFont(); - void synchronize(); -}; - -struct pButton : public pWidget { - Button& button; - HBITMAP hbitmap; - HIMAGELIST himagelist; - - Size minimumSize(); - void setBordered(bool bordered); - void setImage(const image& image, Orientation orientation); - void setText(string text); - - pButton(Button& button) : pWidget(button), button(button), hbitmap(0), himagelist(0) {} - void constructor(); - void destructor(); - void orphan(); - - void onActivate(); -}; - -struct pCanvas : public pWidget { - Canvas& canvas; - uint32_t* surface = nullptr; - unsigned surfaceWidth = 0; - unsigned surfaceHeight = 0; - - void setDroppable(bool droppable); - void setGeometry(Geometry geometry); - void setMode(Canvas::Mode mode); - void setSize(Size size); - - pCanvas(Canvas& canvas) : pWidget(canvas), canvas(canvas) {} - void constructor(); - void destructor(); - void orphan(); - void paint(); - void rasterize(); - void redraw(); - void release(); -}; - -struct pCheckButton : public pWidget { - CheckButton& checkButton; - HBITMAP hbitmap; - HIMAGELIST himagelist; - - Size minimumSize(); - void setChecked(bool checked); - void setImage(const image& image, Orientation orientation); - void setText(string text); - - pCheckButton(CheckButton& checkButton) : pWidget(checkButton), checkButton(checkButton) {} - void constructor(); - void destructor(); - void orphan(); - - void onToggle(); -}; - -struct pCheckLabel : public pWidget { - CheckLabel& checkLabel; - - Size minimumSize(); - void setChecked(bool checked); - void setText(string text); - - pCheckLabel(CheckLabel& checkLabel) : pWidget(checkLabel), checkLabel(checkLabel) {} - void constructor(); - void destructor(); - void orphan(); - - void onToggle(); -}; - -struct pComboButton : public pWidget { - ComboButton& comboButton; - - void append(string text); - void remove(unsigned selection); - Size minimumSize(); - void reset(); - void setGeometry(Geometry geometry); - void setSelection(unsigned selection); - void setText(unsigned selection, string text); - - pComboButton(ComboButton& comboButton) : pWidget(comboButton), comboButton(comboButton) {} - void constructor(); - void destructor(); - void orphan(); - - void onChange(); -}; - -struct pConsole : public pWidget { - Console& console; - LRESULT CALLBACK (*windowProc)(HWND, UINT, LPARAM, WPARAM); - HBRUSH backgroundBrush = nullptr; - - void print(string text); - void reset(); - void setBackgroundColor(Color color); - void setForegroundColor(Color color); - void setPrompt(string prompt); - - pConsole(Console& console) : pWidget(console), console(console) {} - void constructor(); - void destructor(); - void orphan(); - bool keyPress(unsigned key); -}; - -struct pFrame : public pWidget { - Frame& frame; - - void setEnabled(bool enabled); - void setGeometry(Geometry geometry); - void setText(string text); - void setVisible(bool visible); - - pFrame(Frame& frame) : pWidget(frame), frame(frame) {} - void constructor(); - void destructor(); - void orphan(); -}; - -struct pHexEdit : public pWidget { - HexEdit& hexEdit; - WindowProc windowProc = nullptr; - HWND scrollBar = nullptr; - HBRUSH backgroundBrush = nullptr; - - void setBackgroundColor(Color color); - void setColumns(unsigned columns); - void setForegroundColor(Color color); - void setLength(unsigned length); - void setOffset(unsigned offset); - void setRows(unsigned rows); - void update(); - - pHexEdit(HexEdit& hexEdit) : pWidget(hexEdit), hexEdit(hexEdit) {} - void constructor(); - void destructor(); - void orphan(); - bool keyPress(unsigned key); - signed rows(); - signed rowsScrollable(); - signed scrollPosition(); - void scrollTo(signed position); -}; - -struct pHorizontalScroller : public pWidget { - HorizontalScroller& horizontalScroller; - - Size minimumSize(); - void setLength(unsigned length); - void setPosition(unsigned position); - - pHorizontalScroller(HorizontalScroller& horizontalScroller) : pWidget(horizontalScroller), horizontalScroller(horizontalScroller) {} - void constructor(); - void destructor(); - void orphan(); - - void onChange(WPARAM wparam); -}; - -struct pHorizontalSlider : public pWidget { - HorizontalSlider& horizontalSlider; - - Size minimumSize(); - void setLength(unsigned length); - void setPosition(unsigned position); - - pHorizontalSlider(HorizontalSlider& horizontalSlider) : pWidget(horizontalSlider), horizontalSlider(horizontalSlider) {} - void constructor(); - void destructor(); - void orphan(); - - void onChange(); -}; - -struct pLabel : public pWidget { - Label& label; - - Size minimumSize(); - void setText(string text); - - pLabel(Label& label) : pWidget(label), label(label) {} - void constructor(); - void destructor(); - void orphan(); -}; - -struct pLineEdit : public pWidget { - LineEdit& lineEdit; - HBRUSH backgroundBrush = nullptr; - - Size minimumSize(); - void setBackgroundColor(Color color); - void setEditable(bool editable); - void setForegroundColor(Color color); - void setText(string text); - string text(); - - pLineEdit(LineEdit& lineEdit) : pWidget(lineEdit), lineEdit(lineEdit) {} - void constructor(); - void destructor(); - void orphan(); - - void onChange(); -}; - -struct pListView : public pWidget { - ListView& listView; - HIMAGELIST imageList; - vector> imageMap; - vector images; - bool lostFocus; - - void append(const lstring& text); - void autoSizeColumns(); - void remove(unsigned selection); - void reset(); - void setBackgroundColor(Color color); - void setCheckable(bool checkable); - void setChecked(unsigned selection, bool checked); - void setForegroundColor(Color color); - void setGeometry(Geometry geometry); - void setHeaderText(const lstring& text); - void setHeaderVisible(bool visible); - void setImage(unsigned selection, unsigned position, const image& image); - void setSelected(bool selected); - void setSelection(unsigned selection); - void setText(unsigned selection, unsigned position, string text); - - pListView(ListView& listView) : pWidget(listView), listView(listView), imageList(nullptr) {} - void constructor(); - void destructor(); - void orphan(); - void buildImageList(); - - void onActivate(LPARAM lparam); - void onChange(LPARAM lparam); - LRESULT onCustomDraw(LPARAM lparam); -}; - -struct pProgressBar : public pWidget { - ProgressBar& progressBar; - - Size minimumSize(); - void setPosition(unsigned position); - - pProgressBar(ProgressBar& progressBar) : pWidget(progressBar), progressBar(progressBar) {} - void constructor(); - void destructor(); - void orphan(); -}; - -struct pRadioButton : public pWidget { - RadioButton& radioButton; - HBITMAP hbitmap; - HIMAGELIST himagelist; - - Size minimumSize(); - void setChecked(); - void setGroup(const group& group); - void setImage(const image& image, Orientation orientation); - void setText(string text); - - pRadioButton(RadioButton& radioButton) : pWidget(radioButton), radioButton(radioButton) {} - void constructor(); - void destructor(); - void orphan(); - - void onActivate(); -}; - -struct pRadioLabel : public pWidget { - RadioLabel& radioLabel; - - Size minimumSize(); - void setChecked(); - void setGroup(const group& group); - void setText(string text); - - pRadioLabel(RadioLabel& radioLabel) : pWidget(radioLabel), radioLabel(radioLabel) {} - void constructor(); - void destructor(); - void orphan(); - - void onActivate(); -}; - -struct pTabFrame : public pWidget { - TabFrame& tabFrame; - WindowProc windowProc = nullptr; - HIMAGELIST imageList = nullptr; - - void append(string text, const image& image); - void remove(unsigned selection); - void setEnabled(bool enabled); - void setGeometry(Geometry geometry); - void setImage(unsigned selection, const image& image); - void setSelection(unsigned selection); - void setText(unsigned selection, string text); - void setVisible(bool visible); - - pTabFrame(TabFrame& tabFrame) : pWidget(tabFrame), tabFrame(tabFrame) {} - void constructor(); - void destructor(); - void orphan(); - void buildImageList(); - void synchronizeLayout(); - - void onChange(); - void onDrawItem(LPARAM lparam); -}; - -struct pTextEdit : public pWidget { - TextEdit& textEdit; - HBRUSH backgroundBrush = nullptr; - - void setBackgroundColor(Color color); - void setCursorPosition(unsigned position); - void setEditable(bool editable); - void setForegroundColor(Color color); - void setText(string text); - void setWordWrap(bool wordWrap); - string text(); - - pTextEdit(TextEdit& textEdit) : pWidget(textEdit), textEdit(textEdit) {} - void constructor(); - void destructor(); - void orphan(); - - void onChange(); -}; - -struct pVerticalScroller : public pWidget { - VerticalScroller& verticalScroller; - - Size minimumSize(); - void setLength(unsigned length); - void setPosition(unsigned position); - - pVerticalScroller(VerticalScroller& verticalScroller) : pWidget(verticalScroller), verticalScroller(verticalScroller) {} - void constructor(); - void destructor(); - void orphan(); - - void onChange(WPARAM wparam); -}; - -struct pVerticalSlider : public pWidget { - VerticalSlider& verticalSlider; - - Size minimumSize(); - void setLength(unsigned length); - void setPosition(unsigned position); - - pVerticalSlider(VerticalSlider& verticalSlider) : pWidget(verticalSlider), verticalSlider(verticalSlider) {} - void constructor(); - void destructor(); - void orphan(); - - void onChange(); -}; - -struct pViewport : public pWidget { - Viewport& viewport; - - uintptr_t handle(); - void setDroppable(bool droppable); - - pViewport(Viewport& viewport) : pWidget(viewport), viewport(viewport) {} - void constructor(); - void destructor(); - void orphan(); -}; +using WindowProc = auto CALLBACK (*)(HWND, UINT, WPARAM, LPARAM) -> LRESULT; } + +#define Declare(Name, Base) \ + p##Name(m##Name& reference) : p##Base(reference) {} \ + auto self() const -> m##Name& { return (m##Name&)reference; } \ + auto state() const -> m##Name::State& { return self().state; } \ + auto construct() -> void override; \ + auto destruct() -> void override; \ + auto reconstruct() -> void override { destruct(), construct(); } \ + +#include "font.hpp" +#include "desktop.hpp" +#include "monitor.hpp" +#include "keyboard.hpp" +#include "mouse.hpp" +#include "browser-window.hpp" +#include "message-window.hpp" + +#include "object.hpp" +#include "group.hpp" + +#include "hotkey.hpp" +#include "timer.hpp" +#include "window.hpp" +#include "status-bar.hpp" +#include "menu-bar.hpp" +#include "popup-menu.hpp" + +#include "action/action.hpp" +#include "action/menu.hpp" +#include "action/menu-separator.hpp" +#include "action/menu-item.hpp" +#include "action/menu-check-item.hpp" +#include "action/menu-radio-item.hpp" + +#include "sizable.hpp" +#include "layout.hpp" + +#include "widget/widget.hpp" +#include "widget/button.hpp" +#include "widget/canvas.hpp" +#include "widget/check-button.hpp" +#include "widget/check-label.hpp" +#include "widget/combo-button.hpp" +#include "widget/combo-button-item.hpp" +#include "widget/frame.hpp" +#include "widget/hex-edit.hpp" +#include "widget/horizontal-scroller.hpp" +#include "widget/horizontal-slider.hpp" +#include "widget/label.hpp" +#include "widget/line-edit.hpp" +#include "widget/list-view.hpp" +#include "widget/list-view-column.hpp" +#include "widget/list-view-item.hpp" +#include "widget/list-view-cell.hpp" +#include "widget/progress-bar.hpp" +#include "widget/radio-button.hpp" +#include "widget/radio-label.hpp" +#include "widget/tab-frame.hpp" +#include "widget/tab-frame-item.hpp" +#include "widget/text-edit.hpp" +#include "widget/vertical-scroller.hpp" +#include "widget/vertical-slider.hpp" +#include "widget/viewport.hpp" + +#include "application.hpp" + +#undef Declare diff --git a/hiro/windows/popup-menu.cpp b/hiro/windows/popup-menu.cpp new file mode 100644 index 00000000..3ef1058d --- /dev/null +++ b/hiro/windows/popup-menu.cpp @@ -0,0 +1,109 @@ +#if defined(Hiro_PopupMenu) + +namespace hiro { + +auto pPopupMenu::construct() -> void { + hwnd = CreateWindow(L"hiroPopupMenu", L"", ResizableStyle, 0, 0, 0, 0, 0, 0, GetModuleHandle(0), 0); +} + +auto pPopupMenu::destruct() -> void { + if(hmenu) { DestroyMenu(hmenu); hmenu = nullptr; } + DestroyWindow(hwnd); +} + +auto pPopupMenu::append(sAction action) -> void { +} + +auto pPopupMenu::remove(sAction action) -> void { +} + +auto pPopupMenu::setFont(const string& font) -> void { +} + +auto pPopupMenu::setVisible(bool visible) -> void { + if(!visible) return; + + if(hmenu) DestroyMenu(hmenu); + hmenu = CreatePopupMenu(); + + MENUINFO mi{sizeof(MENUINFO)}; + mi.fMask = MIM_STYLE; + mi.dwStyle = MNS_NOTIFYBYPOS; //| MNS_MODELESS; + SetMenuInfo(hmenu, &mi); + + unsigned position = 0; + + for(auto& action : state().actions) { + if(!action->self()) continue; + action->self()->position = position; + unsigned enabled = action->enabled() ? 0 : MF_GRAYED; + + MENUITEMINFO mii{sizeof(MENUITEMINFO)}; + mii.fMask = MIIM_DATA; + mii.dwItemData = (ULONG_PTR)action.data(); + + if(auto menu = dynamic_cast(action.data())) { + if(menu->visible()) { + menu->self()->_update(); + AppendMenu(hmenu, MF_STRING | MF_POPUP | enabled, (UINT_PTR)menu->self()->hmenu, utf16_t(menu->text())); + if(auto bitmap = menu->self()->hbitmap) { + //Windows XP and below displays MIIM_BITMAP + hbmpItem in its own column (separate from check/radio marks) + //this causes too much spacing, so use a custom checkmark image instead + mii.fMask |= MIIM_CHECKMARKS; + mii.hbmpUnchecked = bitmap; + } + SetMenuItemInfo(hmenu, position++, true, &mii); + } + } + + #if defined(Hiro_MenuSeparator) + else if(auto menuSeparator = dynamic_cast(action.data())) { + if(menuSeparator->visible()) { + AppendMenu(hmenu, MF_SEPARATOR | enabled, position, L""); + SetMenuItemInfo(hmenu, position++, true, &mii); + } + } + #endif + + #if defined(Hiro_MenuItem) + else if(auto menuItem = dynamic_cast(action.data())) { + if(menuItem->visible()) { + AppendMenu(hmenu, MF_STRING | enabled, position, utf16_t(menuItem->text())); + if(auto bitmap = menuItem->self()->hbitmap) { + mii.fMask |= MIIM_CHECKMARKS; + mii.hbmpUnchecked = bitmap; + } + SetMenuItemInfo(hmenu, position++, true, &mii); + } + } + #endif + + #if defined(Hiro_MenuCheckItem) + else if(auto menuCheckItem = dynamic_cast(action.data())) { + if(menuCheckItem->visible()) { + AppendMenu(hmenu, MF_STRING | enabled, position, utf16_t(menuCheckItem->text())); + SetMenuItemInfo(hmenu, position++, true, &mii); + if(menuCheckItem->checked()) menuCheckItem->setChecked(); + } + } + #endif + + #if defined(Hiro_MenuRadioItem) + else if(auto menuRadioItem = dynamic_cast(action.data())) { + if(menuRadioItem->visible()) { + AppendMenu(hmenu, MF_STRING | enabled, position, utf16_t(menuRadioItem->text())); + SetMenuItemInfo(hmenu, position++, true, &mii); + if(menuRadioItem->checked()) menuRadioItem->setChecked(); + } + } + #endif + } + + POINT point{0}; + GetCursorPos(&point); + TrackPopupMenu(hmenu, TPM_LEFTALIGN | TPM_TOPALIGN, point.x, point.y, 0, hwnd, nullptr); +} + +} + +#endif diff --git a/hiro/windows/popup-menu.hpp b/hiro/windows/popup-menu.hpp new file mode 100644 index 00000000..34fe094a --- /dev/null +++ b/hiro/windows/popup-menu.hpp @@ -0,0 +1,19 @@ +#if defined(Hiro_PopupMenu) + +namespace hiro { + +struct pPopupMenu : pObject { + Declare(PopupMenu, Object) + + auto append(sAction action) -> void; + auto remove(sAction action) -> void; + auto setFont(const string& font) -> void override; + auto setVisible(bool visible) -> void override; + + HWND hwnd = nullptr; + HMENU hmenu = nullptr; +}; + +} + +#endif diff --git a/hiro/windows/settings.cpp b/hiro/windows/settings.cpp deleted file mode 100644 index ea3045da..00000000 --- a/hiro/windows/settings.cpp +++ /dev/null @@ -1,5 +0,0 @@ -namespace phoenix { - -static Settings* settings = nullptr; - -} diff --git a/hiro/windows/sizable.cpp b/hiro/windows/sizable.cpp new file mode 100644 index 00000000..e17147bd --- /dev/null +++ b/hiro/windows/sizable.cpp @@ -0,0 +1,19 @@ +#if defined(Hiro_Sizable) + +namespace hiro { + +auto pSizable::construct() -> void { +} + +auto pSizable::destruct() -> void { +} + +auto pSizable::minimumSize() const -> Size { +} + +auto pSizable::setGeometry(Geometry geometry) -> void { +} + +} + +#endif diff --git a/hiro/windows/sizable.hpp b/hiro/windows/sizable.hpp new file mode 100644 index 00000000..5270283f --- /dev/null +++ b/hiro/windows/sizable.hpp @@ -0,0 +1,14 @@ +#if defined(Hiro_Sizable) + +namespace hiro { + +struct pSizable : pObject { + Declare(Sizable, Object) + + virtual auto minimumSize() const -> Size; + virtual auto setGeometry(Geometry geometry) -> void; +}; + +} + +#endif diff --git a/hiro/windows/status-bar.cpp b/hiro/windows/status-bar.cpp new file mode 100644 index 00000000..19b22f8a --- /dev/null +++ b/hiro/windows/status-bar.cpp @@ -0,0 +1,54 @@ +#if defined(Hiro_StatusBar) + +namespace hiro { + +auto pStatusBar::construct() -> void { + if(auto parent = _parent()) { + hwnd = CreateWindow(STATUSCLASSNAME, L"", WS_CHILD | WS_DISABLED, 0, 0, 0, 0, parent->hwnd, nullptr, GetModuleHandle(0), 0); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); + setEnabled(self().enabled(true)); + setFont(self().font(true)); + setText(self().text()); + setVisible(self().visible(true)); + } +} + +auto pStatusBar::destruct() -> void { + if(hfont) { DeleteObject(hfont); hfont = nullptr; } + if(hwnd) { DestroyWindow(hwnd); hwnd = nullptr; } + if(auto parent = _parent()) { + parent->setGeometry(parent->state().geometry); + } +} + +auto pStatusBar::setEnabled(bool enabled) -> void { + //unsupported +} + +auto pStatusBar::setFont(const string& font) -> void { + if(hfont) DeleteObject(hfont); + hfont = pFont::create(font); + if(hwnd) SendMessage(hwnd, WM_SETFONT, (WPARAM)hfont, 0); +} + +auto pStatusBar::setText(const string& text) -> void { + if(hwnd) SendMessage(hwnd, SB_SETTEXT, 0, (LPARAM)(wchar_t*)utf16_t(text)); +} + +auto pStatusBar::setVisible(bool visible) -> void { + if(hwnd) ShowWindow(hwnd, visible ? SW_SHOWNORMAL : SW_HIDE); + if(auto parent = _parent()) { + parent->setGeometry(parent->state().geometry); + } +} + +auto pStatusBar::_parent() -> maybe { + if(auto parent = self().parentWindow(true)) { + if(auto self = parent->self()) return *self; + } + return nothing; +} + +} + +#endif diff --git a/hiro/windows/status-bar.hpp b/hiro/windows/status-bar.hpp new file mode 100644 index 00000000..75df1012 --- /dev/null +++ b/hiro/windows/status-bar.hpp @@ -0,0 +1,21 @@ +#if defined(Hiro_StatusBar) + +namespace hiro { + +struct pStatusBar : pObject { + Declare(StatusBar, Object) + + auto setEnabled(bool enabled) -> void override; + auto setFont(const string& font) -> void override; + auto setText(const string& text) -> void; + auto setVisible(bool visible) -> void override; + + auto _parent() -> maybe; + + HWND hwnd = 0; + HFONT hfont = 0; +}; + +} + +#endif diff --git a/hiro/windows/timer.cpp b/hiro/windows/timer.cpp index 8c209b2f..c046389a 100644 --- a/hiro/windows/timer.cpp +++ b/hiro/windows/timer.cpp @@ -1,35 +1,39 @@ -namespace phoenix { +#if defined(Hiro_Timer) + +namespace hiro { static vector timers; -static void CALLBACK Timer_timeoutProc(HWND hwnd, UINT msg, UINT_PTR timerID, DWORD time) { +static auto CALLBACK Timer_timeoutProc(HWND hwnd, UINT msg, UINT_PTR timerID, DWORD time) -> void { for(auto& timer : timers) { - if(timer->htimer == timerID) { - if(timer->timer.onActivate) timer->timer.onActivate(); - return; - } + if(timer->htimer == timerID) return timer->self().doActivate(); } } -void pTimer::setEnabled(bool enabled) { +auto pTimer::construct() -> void { + timers.append(this); + htimer = 0; +} + +auto pTimer::destruct() -> void { +} + +auto pTimer::setEnabled(bool enabled) -> void { if(htimer) { KillTimer(NULL, htimer); htimer = 0; } if(enabled == true) { - htimer = SetTimer(NULL, 0u, timer.state.interval, Timer_timeoutProc); + htimer = SetTimer(NULL, 0u, state().interval, Timer_timeoutProc); } } -void pTimer::setInterval(unsigned interval) { +auto pTimer::setInterval(unsigned interval) -> void { //destroy and recreate timer if interval changed - setEnabled(timer.state.enabled); -} - -void pTimer::constructor() { - timers.append(this); - htimer = 0; + setEnabled(self().enabled(true)); } } + +#endif diff --git a/hiro/windows/timer.hpp b/hiro/windows/timer.hpp new file mode 100644 index 00000000..c4207f80 --- /dev/null +++ b/hiro/windows/timer.hpp @@ -0,0 +1,16 @@ +#if defined(Hiro_Timer) + +namespace hiro { + +struct pTimer : pObject { + Declare(Timer, Object) + + auto setEnabled(bool enabled) -> void override; + auto setInterval(unsigned interval) -> void; + + UINT_PTR htimer; +}; + +} + +#endif diff --git a/hiro/windows/utility.cpp b/hiro/windows/utility.cpp index ce692b59..0898e4a4 100644 --- a/hiro/windows/utility.cpp +++ b/hiro/windows/utility.cpp @@ -1,18 +1,18 @@ -namespace phoenix { +namespace hiro { static const unsigned Windows2000 = 0x0500; static const unsigned WindowsXP = 0x0501; static const unsigned WindowsVista = 0x0600; static const unsigned Windows7 = 0x0601; -static unsigned OsVersion() { +static auto OsVersion() -> unsigned { OSVERSIONINFO versionInfo = {0}; versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); GetVersionEx(&versionInfo); return (versionInfo.dwMajorVersion << 8) + (versionInfo.dwMajorVersion << 0); } -static HBITMAP CreateBitmap(const image& image) { +static auto CreateBitmap(const image& image) -> HBITMAP { HDC hdc = GetDC(0); BITMAPINFO bitmapInfo; memset(&bitmapInfo, 0, sizeof(BITMAPINFO)); @@ -30,12 +30,16 @@ static HBITMAP CreateBitmap(const image& image) { return hbitmap; } -static lstring DropPaths(WPARAM wparam) { +static auto CreateRGB(const Color& color) -> COLORREF { + return RGB(color.red(), color.green(), color.blue()); +} + +static auto DropPaths(WPARAM wparam) -> lstring { auto dropList = HDROP(wparam); auto fileCount = DragQueryFile(dropList, ~0u, nullptr, 0); lstring paths; - for(unsigned n = 0; n < fileCount; n++) { + for(auto n : range(fileCount)) { auto length = DragQueryFile(dropList, n, nullptr, 0); auto buffer = new wchar_t[length + 1]; @@ -52,29 +56,13 @@ static lstring DropPaths(WPARAM wparam) { return paths; } -static Layout* GetParentWidgetLayout(Sizable* sizable) { - while(sizable) { - if(sizable->state.parent && dynamic_cast(sizable->state.parent)) return (Layout*)sizable; - sizable = sizable->state.parent; - } - return nullptr; -} - -static Widget* GetParentWidget(Sizable* sizable) { - while(sizable) { - if(sizable->state.parent && dynamic_cast(sizable->state.parent)) return (Widget*)sizable->state.parent; - sizable = sizable->state.parent; - } - return nullptr; -} - -static unsigned GetWindowZOrder(HWND hwnd) { +static auto GetWindowZOrder(HWND hwnd) -> unsigned { unsigned z = 0; for(HWND next = hwnd; next != NULL; next = GetWindow(next, GW_HWNDPREV)) z++; return z; } -static void ImageList_Append(HIMAGELIST imageList, const nall::image& source, unsigned scale) { +static auto ImageList_Append(HIMAGELIST imageList, const image& source, unsigned scale) -> void { auto image = source; if(image.empty()) { image.allocate(scale, scale); @@ -87,128 +75,15 @@ static void ImageList_Append(HIMAGELIST imageList, const nall::image& source, un DeleteObject(bitmap); } -static Keyboard::Keycode Keysym(unsigned keysym, unsigned keyflags) { - #define pressed(keysym) (GetAsyncKeyState(keysym) & 0x8000) - #define enabled(keysym) (GetKeyState(keysym)) - #define shifted() (pressed(VK_LSHIFT) || pressed(VK_RSHIFT)) - #define extended() (keyflags & (1 << 24)) - - switch(keysym) { - case VK_ESCAPE: return Keyboard::Keycode::Escape; - case VK_F1: return Keyboard::Keycode::F1; - case VK_F2: return Keyboard::Keycode::F2; - case VK_F3: return Keyboard::Keycode::F3; - case VK_F4: return Keyboard::Keycode::F4; - case VK_F5: return Keyboard::Keycode::F5; - case VK_F6: return Keyboard::Keycode::F6; - case VK_F7: return Keyboard::Keycode::F7; - case VK_F8: return Keyboard::Keycode::F8; - case VK_F9: return Keyboard::Keycode::F9; - //Keyboard::Keycode::F10 (should be captured under VK_MENU from WM_SYSKEY(UP,DOWN); but this is not working...) - case VK_F11: return Keyboard::Keycode::F11; - case VK_F12: return Keyboard::Keycode::F12; - - //Keyboard::Keycode::PrintScreen - //Keyboard::Keycode::SysRq - case VK_SCROLL: return Keyboard::Keycode::ScrollLock; - case VK_PAUSE: return Keyboard::Keycode::Pause; - //Keyboard::Keycode::Break - - case VK_INSERT: return extended() ? Keyboard::Keycode::Insert : Keyboard::Keycode::KeypadInsert; - case VK_DELETE: return extended() ? Keyboard::Keycode::Delete : Keyboard::Keycode::KeypadDelete; - case VK_HOME: return extended() ? Keyboard::Keycode::Home : Keyboard::Keycode::KeypadHome; - case VK_END: return extended() ? Keyboard::Keycode::End : Keyboard::Keycode::KeypadEnd; - case VK_PRIOR: return extended() ? Keyboard::Keycode::PageUp : Keyboard::Keycode::KeypadPageUp; - case VK_NEXT: return extended() ? Keyboard::Keycode::PageDown : Keyboard::Keycode::KeypadPageDown; - - case VK_UP: return extended() ? Keyboard::Keycode::Up : Keyboard::Keycode::KeypadUp; - case VK_DOWN: return extended() ? Keyboard::Keycode::Down : Keyboard::Keycode::KeypadDown; - case VK_LEFT: return extended() ? Keyboard::Keycode::Left : Keyboard::Keycode::KeypadLeft; - case VK_RIGHT: return extended() ? Keyboard::Keycode::Right : Keyboard::Keycode::KeypadRight; - - case VK_OEM_3: return !shifted() ? Keyboard::Keycode::Grave : Keyboard::Keycode::Tilde; - case '1': return !shifted() ? Keyboard::Keycode::Number1 : Keyboard::Keycode::Exclamation; - case '2': return !shifted() ? Keyboard::Keycode::Number2 : Keyboard::Keycode::At; - case '3': return !shifted() ? Keyboard::Keycode::Number3 : Keyboard::Keycode::Pound; - case '4': return !shifted() ? Keyboard::Keycode::Number4 : Keyboard::Keycode::Dollar; - case '5': return !shifted() ? Keyboard::Keycode::Number5 : Keyboard::Keycode::Percent; - case '6': return !shifted() ? Keyboard::Keycode::Number6 : Keyboard::Keycode::Power; - case '7': return !shifted() ? Keyboard::Keycode::Number7 : Keyboard::Keycode::Ampersand; - case '8': return !shifted() ? Keyboard::Keycode::Number8 : Keyboard::Keycode::Asterisk; - case '9': return !shifted() ? Keyboard::Keycode::Number9 : Keyboard::Keycode::ParenthesisLeft; - case '0': return !shifted() ? Keyboard::Keycode::Number0 : Keyboard::Keycode::ParenthesisRight; - case VK_OEM_MINUS: return !shifted() ? Keyboard::Keycode::Minus : Keyboard::Keycode::Underscore; - case VK_OEM_PLUS: return !shifted() ? Keyboard::Keycode::Equal : Keyboard::Keycode::Plus; - case VK_BACK: return Keyboard::Keycode::Backspace; - - case VK_OEM_4: return !shifted() ? Keyboard::Keycode::BracketLeft : Keyboard::Keycode::BraceLeft; - case VK_OEM_6: return !shifted() ? Keyboard::Keycode::BracketRight : Keyboard::Keycode::BraceRight; - case VK_OEM_5: return !shifted() ? Keyboard::Keycode::Backslash : Keyboard::Keycode::Pipe; - case VK_OEM_1: return !shifted() ? Keyboard::Keycode::Semicolon : Keyboard::Keycode::Colon; - case VK_OEM_7: return !shifted() ? Keyboard::Keycode::Apostrophe : Keyboard::Keycode::Quote; - case VK_OEM_COMMA: return !shifted() ? Keyboard::Keycode::Comma : Keyboard::Keycode::CaretLeft; - case VK_OEM_PERIOD: return !shifted() ? Keyboard::Keycode::Period : Keyboard::Keycode::CaretRight; - case VK_OEM_2: return !shifted() ? Keyboard::Keycode::Slash : Keyboard::Keycode::Question; - - case VK_TAB: return Keyboard::Keycode::Tab; - case VK_CAPITAL: return Keyboard::Keycode::CapsLock; - case VK_RETURN: return !extended() ? Keyboard::Keycode::Return : Keyboard::Keycode::Enter; - case VK_SHIFT: return !pressed(VK_RSHIFT) ? Keyboard::Keycode::ShiftLeft : Keyboard::Keycode::ShiftRight; - case VK_CONTROL: return !pressed(VK_RCONTROL) ? Keyboard::Keycode::ControlLeft : Keyboard::Keycode::ControlRight; - case VK_LWIN: return Keyboard::Keycode::SuperLeft; - case VK_RWIN: return Keyboard::Keycode::SuperRight; - case VK_MENU: - if(keyflags & (1 << 24)) return Keyboard::Keycode::AltRight; - return Keyboard::Keycode::AltLeft; - case VK_SPACE: return Keyboard::Keycode::Space; - case VK_APPS: return Keyboard::Keycode::Menu; - - case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': - case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': - if(enabled(VK_CAPITAL)) { - if(shifted()) { - return (Keyboard::Keycode)((unsigned)Keyboard::Keycode::a + keysym - 'A'); - } else { - return (Keyboard::Keycode)((unsigned)Keyboard::Keycode::A + keysym - 'A'); - } - } else { - if(shifted()) { - return (Keyboard::Keycode)((unsigned)Keyboard::Keycode::A + keysym - 'A'); - } else { - return (Keyboard::Keycode)((unsigned)Keyboard::Keycode::a + keysym - 'A'); - } - } - break; - - case VK_NUMLOCK: return Keyboard::Keycode::NumLock; - case VK_DIVIDE: return Keyboard::Keycode::Divide; - case VK_MULTIPLY: return Keyboard::Keycode::Multiply; - case VK_SUBTRACT: return Keyboard::Keycode::Subtract; - case VK_ADD: return Keyboard::Keycode::Add; - case VK_DECIMAL: return Keyboard::Keycode::Point; - case VK_NUMPAD1: return Keyboard::Keycode::Keypad1; - case VK_NUMPAD2: return Keyboard::Keycode::Keypad2; - case VK_NUMPAD3: return Keyboard::Keycode::Keypad3; - case VK_NUMPAD4: return Keyboard::Keycode::Keypad4; - case VK_NUMPAD5: return Keyboard::Keycode::Keypad5; - case VK_NUMPAD6: return Keyboard::Keycode::Keypad6; - case VK_NUMPAD7: return Keyboard::Keycode::Keypad7; - case VK_NUMPAD8: return Keyboard::Keycode::Keypad8; - case VK_NUMPAD9: return Keyboard::Keycode::Keypad9; - case VK_NUMPAD0: return Keyboard::Keycode::Keypad0; - - case VK_CLEAR: return Keyboard::Keycode::KeypadCenter; +//post message only if said message is not already pending in the queue +static auto PostMessageOnce(HWND hwnd, UINT id, WPARAM wparam, LPARAM lparam) -> void { + MSG msg; + if(!PeekMessage(&msg, hwnd, id, id, PM_NOREMOVE)) { + PostMessage(hwnd, id, wparam, lparam); } - - return Keyboard::Keycode::None; - - #undef pressed - #undef enabled - #undef shifted - #undef extended } -static unsigned ScrollEvent(HWND hwnd, WPARAM wparam) { +static auto ScrollEvent(HWND hwnd, WPARAM wparam) -> unsigned { SCROLLINFO info; memset(&info, 0, sizeof(SCROLLINFO)); info.cbSize = sizeof(SCROLLINFO); @@ -233,121 +108,261 @@ static unsigned ScrollEvent(HWND hwnd, WPARAM wparam) { return info.nPos; } -static LRESULT CALLBACK Shared_windowProc(WindowProc windowProc, HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { - Object* object = (Object*)GetWindowLongPtr(hwnd, GWLP_USERDATA); - if(object == nullptr) return DefWindowProc(hwnd, msg, wparam, lparam); - Window& window = dynamic_cast(object) ? *(Window*)object : *((Widget*)object)->Sizable::state.window; +//separate because PopupMenu HWND does not contain GWLP_USERDATA pointing at Window needed for Shared_windowProc +static auto CALLBACK Menu_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT { + switch(msg) { + case WM_MENUCOMMAND: { + MENUITEMINFO mii{sizeof(MENUITEMINFO)}; + mii.fMask = MIIM_DATA; + GetMenuItemInfo((HMENU)lparam, wparam, true, &mii); + + auto object = (mObject*)mii.dwItemData; + if(!object) break; + + #if defined(Hiro_MenuItem) + if(auto menuItem = dynamic_cast(object)) { + return menuItem->self()->onActivate(), false; + } + #endif + + #if defined(Hiro_MenuCheckItem) + if(auto menuCheckItem = dynamic_cast(object)) { + return menuCheckItem->self()->onToggle(), false; + } + #endif + + #if defined(Hiro_MenuRadioItem) + if(auto menuRadioItem = dynamic_cast(object)) { + return menuRadioItem->self()->onActivate(), false; + } + #endif + + break; + } + } + + return DefWindowProc(hwnd, msg, wparam, lparam); +} + +static auto CALLBACK Shared_windowProc(WindowProc windowProc, HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT { + auto object = (mObject*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if(!object) return DefWindowProc(hwnd, msg, wparam, lparam); + auto window = dynamic_cast(object); + if(!window) window = object->parentWindow(true); + if(!window) return DefWindowProc(hwnd, msg, wparam, lparam); bool process = true; - if(!pWindow::modal.empty() && !pWindow::modal.find(&window.p)) process = false; - if(applicationState.quit) process = false; - if(process == false) return DefWindowProc(hwnd, msg, wparam, lparam); + if(pWindow::modal && !pWindow::modal.find(window->self())) process = false; + if(Application::state.quit) process = false; + if(!process) return DefWindowProc(hwnd, msg, wparam, lparam); switch(msg) { case WM_CTLCOLORBTN: case WM_CTLCOLOREDIT: case WM_CTLCOLORSTATIC: { - Object* object = (Object*)GetWindowLongPtr((HWND)lparam, GWLP_USERDATA); - if(object == nullptr) break; + auto object = (mObject*)GetWindowLongPtr((HWND)lparam, GWLP_USERDATA); + if(!object) break; + //allow custom colors for various widgets //note that this happens always: default colors are black text on a white background, unless overridden //this intentionally overrides the default behavior of Windows to paint disabled controls with the window background color - if(dynamic_cast(object)) { - Console& console = *(Console*)object; - Color& background = console.state.backgroundColor; - Color& foreground = console.state.foregroundColor; - SetTextColor((HDC)wparam, RGB(foreground.red, foreground.green, foreground.blue)); - SetBkColor((HDC)wparam, RGB(background.red, background.green, background.blue)); - return (LRESULT)console.p.backgroundBrush; - } else if(dynamic_cast(object)) { - HexEdit& hexEdit = *(HexEdit*)object; - Color& background = hexEdit.state.backgroundColor; - Color& foreground = hexEdit.state.foregroundColor; - SetTextColor((HDC)wparam, RGB(foreground.red, foreground.green, foreground.blue)); - SetBkColor((HDC)wparam, RGB(background.red, background.green, background.blue)); - return (LRESULT)hexEdit.p.backgroundBrush; - } else if(dynamic_cast(object)) { - LineEdit& lineEdit = *(LineEdit*)object; - Color& background = lineEdit.state.backgroundColor; - Color& foreground = lineEdit.state.foregroundColor; - SetTextColor((HDC)wparam, RGB(foreground.red, foreground.green, foreground.blue)); - SetBkColor((HDC)wparam, RGB(background.red, background.green, background.blue)); - return (LRESULT)lineEdit.p.backgroundBrush; - } else if(dynamic_cast(object)) { - TextEdit& textEdit = *(TextEdit*)object; - Color& background = textEdit.state.backgroundColor; - Color& foreground = textEdit.state.foregroundColor; - SetTextColor((HDC)wparam, RGB(foreground.red, foreground.green, foreground.blue)); - SetBkColor((HDC)wparam, RGB(background.red, background.green, background.blue)); - return (LRESULT)textEdit.p.backgroundBrush; - } else if(!GetParentWidget((Sizable*)object) && window.p.brush) { - SetBkColor((HDC)wparam, window.p.brushColor); - return (INT_PTR)window.p.brush; + + #if defined(Hiro_Window) && defined(Hiro_TabFrame) + if(!object->parentTabFrame(true) && window->self()->hbrush) { + SetBkColor((HDC)wparam, window->self()->hbrushColor); + return (LRESULT)window->self()->hbrush; } + #endif + + #if defined(Hiro_HexEdit) + if(auto hexEdit = dynamic_cast(object)) { + if(auto background = hexEdit->backgroundColor()) SetBkColor((HDC)wparam, CreateRGB(background)); + if(auto foreground = hexEdit->foregroundColor()) SetTextColor((HDC)wparam, CreateRGB(foreground)); + return (LRESULT)hexEdit->self()->backgroundBrush; + } + #endif + + #if defined(Hiro_LineEdit) + if(auto lineEdit = dynamic_cast(object)) { + if(auto background = lineEdit->backgroundColor()) SetBkColor((HDC)wparam, CreateRGB(background)); + if(auto foreground = lineEdit->foregroundColor()) SetTextColor((HDC)wparam, CreateRGB(foreground)); + return (LRESULT)lineEdit->self()->backgroundBrush; + } + #endif + + #if defined(Hiro_TextEdit) + if(auto textEdit = dynamic_cast(object)) { + if(auto background = textEdit->backgroundColor()) SetBkColor((HDC)wparam, CreateRGB(background)); + if(auto foreground = textEdit->foregroundColor()) SetTextColor((HDC)wparam, CreateRGB(foreground)); + return (LRESULT)textEdit->self()->backgroundBrush; + } + #endif + break; } case WM_DRAWITEM: { - unsigned id = LOWORD(wparam); - HWND control = GetDlgItem(hwnd, id); - Object* object = (Object*)GetWindowLongPtr(control, GWLP_USERDATA); - if(object == nullptr) break; - if(dynamic_cast(object)) { ((TabFrame*)object)->p.onDrawItem(lparam); return TRUE; } + auto drawItem = (LPDRAWITEMSTRUCT)lparam; + auto object = (mObject*)GetWindowLongPtr((HWND)drawItem->hwndItem, GWLP_USERDATA); + if(!object) break; + + #if defined(Hiro_TabFrame) + if(auto tabFrame = dynamic_cast(object)) { + return tabFrame->self()->onDrawItem(lparam), true; + } + #endif + break; } + case WM_MENUCOMMAND: { + return Menu_windowProc(hwnd, msg, wparam, lparam); + } + case WM_COMMAND: { - unsigned id = LOWORD(wparam); - HWND control = GetDlgItem(hwnd, id); - Object* object = control ? (Object*)GetWindowLongPtr(control, GWLP_USERDATA) : pObject::find(id); - if(object == nullptr) break; - if(dynamic_cast(object)) { ((Item*)object)->p.onActivate(); return FALSE; } - if(dynamic_cast(object)) { ((CheckItem*)object)->p.onToggle(); return FALSE; } - if(dynamic_cast(object)) { ((RadioItem*)object)->p.onActivate(); return FALSE; } - if(dynamic_cast(object)) { ((Button*)object)->p.onActivate(); return FALSE; } - if(dynamic_cast(object)) { ((CheckButton*)object)->p.onToggle(); return FALSE; } - if(dynamic_cast(object)) { ((CheckLabel*)object)->p.onToggle(); return FALSE; } - if(dynamic_cast(object) && HIWORD(wparam) == CBN_SELCHANGE) { ((ComboButton*)object)->p.onChange(); return FALSE; } - if(dynamic_cast(object) && HIWORD(wparam) == EN_CHANGE) { ((LineEdit*)object)->p.onChange(); return FALSE; } - if(dynamic_cast(object)) { ((RadioButton*)object)->p.onActivate(); return FALSE; } - if(dynamic_cast(object)) { ((RadioLabel*)object)->p.onActivate(); return FALSE; } - if(dynamic_cast(object) && HIWORD(wparam) == EN_CHANGE) { ((TextEdit*)object)->p.onChange(); return FALSE; } + if(!lparam) break; + auto object = (mObject*)GetWindowLongPtr((HWND)lparam, GWLP_USERDATA); + if(!object) break; + + #if defined(Hiro_Button) + if(auto button = dynamic_cast(object)) { + return button->self()->onActivate(), false; + } + #endif + + #if defined(Hiro_CheckButton) + if(auto checkButton = dynamic_cast(object)) { + return checkButton->self()->onToggle(), false; + } + #endif + + #if defined(Hiro_CheckLabel) + if(auto checkLabel = dynamic_cast(object)) { + return checkLabel->self()->onToggle(), false; + } + #endif + + #if defined(Hiro_ComboButton) + if(auto comboButton = dynamic_cast(object)) { + if(HIWORD(wparam) == CBN_SELCHANGE) { + return comboButton->self()->onChange(), false; + } + } + #endif + + #if defined(Hiro_LineEdit) + if(auto lineEdit = dynamic_cast(object)) { + if(HIWORD(wparam) == EN_CHANGE) { + return lineEdit->self()->onChange(), false; + } + } + #endif + + #if defined(Hiro_RadioButton) + if(auto radioButton = dynamic_cast(object)) { + return radioButton->self()->onActivate(), false; + } + #endif + + #if defined(Hiro_RadioLabel) + if(auto radioLabel = dynamic_cast(object)) { + return radioLabel->self()->onActivate(), false; + } + #endif + + #if defined(Hiro_TextEdit) + if(auto textEdit = dynamic_cast(object)) { + if(HIWORD(wparam) == EN_CHANGE) { + return textEdit->self()->onChange(), false; + } + } + #endif + break; } case WM_NOTIFY: { - unsigned id = LOWORD(wparam); - HWND control = GetDlgItem(hwnd, id); - Object* object = (Object*)GetWindowLongPtr(control, GWLP_USERDATA); - if(object == nullptr) break; - if(dynamic_cast(object) && ((LPNMHDR)lparam)->code == LVN_ITEMACTIVATE) { ((ListView*)object)->p.onActivate(lparam); break; } - if(dynamic_cast(object) && ((LPNMHDR)lparam)->code == LVN_ITEMCHANGED) { ((ListView*)object)->p.onChange(lparam); break; } - if(dynamic_cast(object) && ((LPNMHDR)lparam)->code == NM_CUSTOMDRAW) { return ((ListView*)object)->p.onCustomDraw(lparam); } - if(dynamic_cast(object) && ((LPNMHDR)lparam)->code == TCN_SELCHANGE) { ((TabFrame*)object)->p.onChange(); break; } + auto header = (LPNMHDR)lparam; + auto object = (mObject*)GetWindowLongPtr((HWND)header->hwndFrom, GWLP_USERDATA); + if(!object) break; + + #if defined(Hiro_ListView) + if(auto listView = dynamic_cast(object)) { + if(header->code == LVN_ITEMACTIVATE) { + listView->self()->onActivate(lparam); + break; + } + if(header->code == LVN_ITEMCHANGED) { + listView->self()->onChange(lparam); + break; + } + if(header->code == LVN_COLUMNCLICK) { + listView->self()->onSort(lparam); + break; + } + if(header->code == NM_RCLICK) { + listView->self()->onContext(lparam); + break; + } + if(header->code == NM_CUSTOMDRAW) { + return listView->self()->onCustomDraw(lparam); + } + } + #endif + + #if defined(Hiro_TabFrame) + if(auto tabFrame = dynamic_cast(object)) { + if(header->code == TCN_SELCHANGE) { + tabFrame->self()->onChange(); + break; + } + } + #endif + break; } - case WM_APP + AppMessage::ListView_onActivate: { - ListView* listView = (ListView*)lparam; - if(listView && listView->onActivate) listView->onActivate(); + #if defined(Hiro_ListView) + case AppMessage::ListView_onActivate: { + if(auto listView = (mListView*)lparam) listView->doActivate(); break; } + case AppMessage::ListView_onChange: { + if(auto listView = (mListView*)lparam) listView->doChange(); + } + #endif + case WM_HSCROLL: case WM_VSCROLL: { - Object* object = nullptr; - if(lparam) { - object = (Object*)GetWindowLongPtr((HWND)lparam, GWLP_USERDATA); - } else { - unsigned id = LOWORD(wparam); - HWND control = GetDlgItem(hwnd, id); - object = (Object*)GetWindowLongPtr(control, GWLP_USERDATA); + if(!lparam) break; + auto object = (mObject*)GetWindowLongPtr((HWND)lparam, GWLP_USERDATA); + if(!object) break; + + #if defined(Hiro_HorizontalScroller) + if(auto horizontalScroller = dynamic_cast(object)) { + return horizontalScroller->self()->onChange(wparam), true; } - if(object == nullptr) break; - if(dynamic_cast(object)) { ((HorizontalScroller*)object)->p.onChange(wparam); return TRUE; } - if(dynamic_cast(object)) { ((VerticalScroller*)object)->p.onChange(wparam); return TRUE; } - if(dynamic_cast(object)) { ((HorizontalSlider*)object)->p.onChange(); return TRUE; } - if(dynamic_cast(object)) { ((VerticalSlider*)object)->p.onChange(); return TRUE; } + #endif + + #if defined(Hiro_HorizontalSlider) + if(auto horizontalSlider = dynamic_cast(object)) { + return horizontalSlider->self()->onChange(), true; + } + #endif + + #if defined(Hiro_VerticalScroller) + if(auto verticalScroller = dynamic_cast(object)) { + return verticalScroller->self()->onChange(wparam), true; + } + #endif + + #if defined(Hiro_VerticalSlider) + if(auto verticalSlider = dynamic_cast(object)) { + return verticalSlider->self()->onChange(), true; + } + #endif + break; } } diff --git a/hiro/windows/widget/button.cpp b/hiro/windows/widget/button.cpp index 26a938f1..98cf7fce 100644 --- a/hiro/windows/widget/button.cpp +++ b/hiro/windows/widget/button.cpp @@ -1,102 +1,94 @@ -namespace phoenix { +#if defined(Hiro_Button) -#ifndef Button_SetImageList - //MinGW/32-bit has painfully outdated platform headers ... - typedef struct { - HIMAGELIST himl; - RECT margin; - UINT uAlign; - } BUTTON_IMAGELIST, *PBUTTON_IMAGELIST; +namespace hiro { - #define BUTTON_IMAGELIST_ALIGN_LEFT 0 - #define BUTTON_IMAGELIST_ALIGN_RIGHT 1 - #define BUTTON_IMAGELIST_ALIGN_TOP 2 - #define BUTTON_IMAGELIST_ALIGN_BOTTOM 3 - #define BUTTON_IMAGELIST_ALIGN_CENTER 4 - - #define BCM_FIRST 0x1600 - #define BCM_SETIMAGELIST (BCM_FIRST+2) - #define Button_SetImageList(hwnd, pbuttonImagelist) (WINBOOL)SNDMSG((hwnd),BCM_SETIMAGELIST,0,(LPARAM)(pbuttonImagelist)) -#endif - -Size pButton::minimumSize() { - Size size = pFont::size(hfont, button.state.text); - - if(button.state.orientation == Orientation::Horizontal) { - size.width += button.state.image.width; - size.height = max(button.state.image.height, size.height); - } - - if(button.state.orientation == Orientation::Vertical) { - size.width = max(button.state.image.width, size.width); - size.height += button.state.image.height; - } - - return {size.width + (button.state.text ? 20 : 10), size.height + 10}; +auto pButton::construct() -> void { + hwnd = CreateWindow( + L"BUTTON", L"", + WS_CHILD | WS_TABSTOP, + 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); + pWidget::_setState(); + _setState(); + setBordered(state().bordered); } -void pButton::setBordered(bool bordered) { +auto pButton::destruct() -> void { + if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } + if(himagelist) { ImageList_Destroy(himagelist); himagelist = 0; } + DestroyWindow(hwnd); } -void pButton::setImage(const image& image, Orientation orientation) { - nall::image nallImage = image; - nallImage.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); +auto pButton::minimumSize() const -> Size { + auto size = pFont::size(hfont, state().text); + + if(state().orientation == Orientation::Horizontal) { + size.setWidth(size.width() + state().icon.width); + size.setHeight(max(size.height(), state().icon.height)); + } + + if(state().orientation == Orientation::Vertical) { + size.setWidth(max(size.width(), state().icon.width)); + size.setHeight(size.height() + state().icon.height); + } + + return {size.width() + (state().text ? 20 : 13), size.height() + 10}; +} + +auto pButton::setBordered(bool bordered) -> void { +} + +auto pButton::setIcon(const image& icon) -> void { + _setState(); +} + +auto pButton::setOrientation(Orientation orientation) -> void { + _setState(); +} + +auto pButton::setText(const string& text) -> void { + _setState(); +} + +auto pButton::onActivate() -> void { + self().doActivate(); +} + +//performs setIcon, setOrientation, setText +auto pButton::_setState() -> void { + image icon = state().icon; + icon.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } if(himagelist) { ImageList_Destroy(himagelist); himagelist = 0; } //Windows XP and earlier do not support translucent images //so we must blend with the button background color (which does not look great with XP gradient-button themes) - if(OsVersion() < WindowsVista) nallImage.alphaBlend(GetSysColor(COLOR_BTNFACE)); - hbitmap = CreateBitmap(nallImage); - himagelist = ImageList_Create(nallImage.width, nallImage.height, ILC_COLOR32, 1, 0); + if(OsVersion() < WindowsVista) icon.alphaBlend(GetSysColor(COLOR_BTNFACE)); + + hbitmap = CreateBitmap(icon); + himagelist = ImageList_Create(icon.width, icon.height, ILC_COLOR32, 1, 0); ImageList_Add(himagelist, hbitmap, NULL); BUTTON_IMAGELIST list; list.himl = himagelist; - switch(orientation) { + switch(state().orientation) { case Orientation::Horizontal: SetRect(&list.margin, 5, 0, 0, 0); list.uAlign = BUTTON_IMAGELIST_ALIGN_LEFT; break; case Orientation::Vertical: SetRect(&list.margin, 0, 5, 0, 0); list.uAlign = BUTTON_IMAGELIST_ALIGN_TOP; break; } Button_SetImageList(hwnd, &list); - setText(button.state.text); //update text to display nicely with image (or lack thereof) -} - -void pButton::setText(string text) { - if(text.empty()) { + if(auto text = state().text) { + //text will not show up if BS_BITMAP is set + SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) &~ BS_BITMAP); + SetWindowText(hwnd, utf16_t(text)); + } else { //bitmaps will not show up if text is empty SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) | BS_BITMAP); - } else { - //text will not show up if BS_BITMAP is set - SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) & ~BS_BITMAP); + SetWindowText(hwnd, L""); } - - SetWindowText(hwnd, utf16_t(text)); -} - -void pButton::constructor() { - hwnd = CreateWindow(L"BUTTON", L"", WS_CHILD | WS_TABSTOP, 0, 0, 0, 0, parentHwnd, (HMENU)id, GetModuleHandle(0), 0); - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&button); - setDefaultFont(); - setBordered(button.state.bordered); - setImage(button.state.image, button.state.orientation); -//setText(button.state.text); //called by setImage(); - synchronize(); -} - -void pButton::destructor() { - if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } - if(himagelist) { ImageList_Destroy(himagelist); himagelist = 0; } - DestroyWindow(hwnd); -} - -void pButton::orphan() { - destructor(); - constructor(); -} - -void pButton::onActivate() { - if(button.onActivate) button.onActivate(); } } + +#endif diff --git a/hiro/windows/widget/button.hpp b/hiro/windows/widget/button.hpp new file mode 100644 index 00000000..12ee854f --- /dev/null +++ b/hiro/windows/widget/button.hpp @@ -0,0 +1,24 @@ +#if defined(Hiro_Button) + +namespace hiro { + +struct pButton : pWidget { + Declare(Button, Widget) + + auto minimumSize() const -> Size override; + auto setBordered(bool bordered) -> void; + auto setIcon(const image& icon) -> void; + auto setOrientation(Orientation orientation) -> void; + auto setText(const string& text) -> void; + + auto onActivate() -> void; + + auto _setState() -> void; + + HBITMAP hbitmap; + HIMAGELIST himagelist; +}; + +} + +#endif diff --git a/hiro/windows/widget/canvas.cpp b/hiro/windows/widget/canvas.cpp index 9e0dc815..a71f7998 100644 --- a/hiro/windows/widget/canvas.cpp +++ b/hiro/windows/widget/canvas.cpp @@ -1,17 +1,16 @@ -namespace phoenix { +#if defined(Hiro_Canvas) -static LRESULT CALLBACK Canvas_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { - Object* object = (Object*)GetWindowLongPtr(hwnd, GWLP_USERDATA); - if(object == nullptr) return DefWindowProc(hwnd, msg, wparam, lparam); - if(!dynamic_cast(object)) return DefWindowProc(hwnd, msg, wparam, lparam); - Canvas& canvas = (Canvas&)*object; +namespace hiro { + +static auto CALLBACK Canvas_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT { + auto object = (mObject*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if(!object) return DefWindowProc(hwnd, msg, wparam, lparam); + auto canvas = dynamic_cast(object); + if(!canvas) return DefWindowProc(hwnd, msg, wparam, lparam); if(msg == WM_DROPFILES) { - lstring paths = DropPaths(wparam); - if(paths.empty() == false) { - if(canvas.onDrop) canvas.onDrop(paths); - } - return FALSE; + if(auto paths = DropPaths(wparam)) canvas->doDrop(paths); + return false; } if(msg == WM_GETDLGCODE) { @@ -20,103 +19,97 @@ static LRESULT CALLBACK Canvas_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LP if(msg == WM_ERASEBKGND) { //background is erased during WM_PAINT to prevent flickering - return TRUE; + return true; } if(msg == WM_PAINT) { - canvas.p.paint(); - return TRUE; + if(auto self = canvas->self()) self->_paint(); + return true; } if(msg == WM_MOUSEMOVE) { - TRACKMOUSEEVENT tracker = {sizeof(TRACKMOUSEEVENT), TME_LEAVE, hwnd}; + TRACKMOUSEEVENT tracker{sizeof(TRACKMOUSEEVENT), TME_LEAVE, hwnd}; TrackMouseEvent(&tracker); - if(canvas.onMouseMove) canvas.onMouseMove({(int16_t)LOWORD(lparam), (int16_t)HIWORD(lparam)}); + canvas->doMouseMove({(int16_t)LOWORD(lparam), (int16_t)HIWORD(lparam)}); } if(msg == WM_MOUSELEAVE) { - if(canvas.onMouseLeave) canvas.onMouseLeave(); + canvas->doMouseLeave(); } if(msg == WM_LBUTTONDOWN || msg == WM_MBUTTONDOWN || msg == WM_RBUTTONDOWN) { - if(canvas.onMousePress) switch(msg) { - case WM_LBUTTONDOWN: canvas.onMousePress(Mouse::Button::Left); break; - case WM_MBUTTONDOWN: canvas.onMousePress(Mouse::Button::Middle); break; - case WM_RBUTTONDOWN: canvas.onMousePress(Mouse::Button::Right); break; + switch(msg) { + case WM_LBUTTONDOWN: canvas->doMousePress(Mouse::Button::Left); break; + case WM_MBUTTONDOWN: canvas->doMousePress(Mouse::Button::Middle); break; + case WM_RBUTTONDOWN: canvas->doMousePress(Mouse::Button::Right); break; } } if(msg == WM_LBUTTONUP || msg == WM_MBUTTONUP || msg == WM_RBUTTONUP) { - if(canvas.onMouseRelease) switch(msg) { - case WM_LBUTTONUP: canvas.onMouseRelease(Mouse::Button::Left); break; - case WM_MBUTTONUP: canvas.onMouseRelease(Mouse::Button::Middle); break; - case WM_RBUTTONUP: canvas.onMouseRelease(Mouse::Button::Right); break; + switch(msg) { + case WM_LBUTTONUP: canvas->doMouseRelease(Mouse::Button::Left); break; + case WM_MBUTTONUP: canvas->doMouseRelease(Mouse::Button::Middle); break; + case WM_RBUTTONUP: canvas->doMouseRelease(Mouse::Button::Right); break; } } return DefWindowProc(hwnd, msg, wparam, lparam); } -void pCanvas::setDroppable(bool droppable) { - DragAcceptFiles(hwnd, droppable); +auto pCanvas::construct() -> void { + hwnd = CreateWindow(L"hiroCanvas", L"", WS_CHILD, 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); + pWidget::_setState(); + setDroppable(state().droppable); + update(); } -void pCanvas::setGeometry(Geometry geometry) { - if(canvas.state.width == 0 || canvas.state.height == 0) rasterize(); - unsigned width = canvas.state.width; - unsigned height = canvas.state.height; - if(width == 0) width = widget.state.geometry.width; - if(height == 0) height = widget.state.geometry.height; - - if(width < geometry.width) { - geometry.x += (geometry.width - width) / 2; - geometry.width = width; - } - - if(height < geometry.height) { - geometry.y += (geometry.height - height) / 2; - geometry.height = height; - } - - pWidget::setGeometry(geometry); -} - -void pCanvas::setMode(Canvas::Mode mode) { - rasterize(), redraw(); -} - -void pCanvas::setSize(Size size) { - rasterize(), redraw(); -} - -void pCanvas::constructor() { - hwnd = CreateWindow(L"phoenix_canvas", L"", WS_CHILD, 0, 0, 0, 0, parentHwnd, (HMENU)id, GetModuleHandle(0), 0); - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&canvas); - setDroppable(canvas.state.droppable); - rasterize(); - synchronize(); -} - -void pCanvas::destructor() { - release(); +auto pCanvas::destruct() -> void { DestroyWindow(hwnd); } -void pCanvas::orphan() { - destructor(); - constructor(); +auto pCanvas::minimumSize() const -> Size { + return {max(0, state().size.width()), max(0, state().size.height())}; } -void pCanvas::paint() { - if(surface == nullptr) return; +auto pCanvas::setColor(Color color) -> void { + mode = Mode::Color; + update(); +} +auto pCanvas::setData(Size size) -> void { + mode = Mode::Data; + update(); +} + +auto pCanvas::setDroppable(bool droppable) -> void { + DragAcceptFiles(hwnd, droppable); +} + +auto pCanvas::setGeometry(Geometry geometry) -> void { + pWidget::setGeometry(geometry); + update(); +} + +auto pCanvas::setGradient(Color topLeft, Color topRight, Color bottomLeft, Color bottomRight) -> void { + mode = Mode::Gradient; + update(); +} + +auto pCanvas::setIcon(const image& icon) -> void { + mode = Mode::Icon; + update(); +} + +auto pCanvas::update() -> void { + _rasterize(); + _redraw(); +} + +auto pCanvas::_paint() -> void { PAINTSTRUCT ps; BeginPaint(hwnd, &ps); - uint32_t* data = surface; - unsigned width = surfaceWidth; - unsigned height = surfaceHeight; - HDC hdc = CreateCompatibleDC(ps.hdc); BITMAPINFO bmi; memset(&bmi, 0, sizeof(BITMAPINFO)); @@ -126,21 +119,17 @@ void pCanvas::paint() { bmi.bmiHeader.biCompression = BI_RGB; bmi.bmiHeader.biWidth = width; bmi.bmiHeader.biHeight = -height; //GDI stores bitmaps upside now; negative height flips bitmap - bmi.bmiHeader.biSizeImage = width * height * sizeof(uint32_t); + bmi.bmiHeader.biSizeImage = pixels.size() * sizeof(uint32_t); void* bits = nullptr; HBITMAP bitmap = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &bits, NULL, 0); - if(bits) memcpy(bits, data, width * height * sizeof(uint32_t)); + if(bits) memory::copy(bits, pixels.data(), pixels.size() * sizeof(uint32_t)); SelectObject(hdc, bitmap); - BLENDFUNCTION bf; - bf.BlendOp = AC_SRC_OVER; - bf.BlendFlags = 0; - bf.SourceConstantAlpha = 255; - bf.AlphaFormat = AC_SRC_ALPHA; - RECT rc; GetClientRect(hwnd, &rc); DrawThemeParentBackground(hwnd, ps.hdc, &rc); + + BLENDFUNCTION bf{AC_SRC_OVER, 0, (BYTE)255, AC_SRC_ALPHA}; AlphaBlend(ps.hdc, 0, 0, width, height, hdc, 0, 0, width, height, bf); DeleteObject(bitmap); @@ -149,61 +138,52 @@ void pCanvas::paint() { EndPaint(hwnd, &ps); } -void pCanvas::rasterize() { - unsigned width = canvas.state.width; - unsigned height = canvas.state.height; - if(width == 0) width = widget.state.geometry.width; - if(height == 0) height = widget.state.geometry.height; +auto pCanvas::_rasterize() -> void { + if(mode == Mode::Color || mode == Mode::Gradient) { + width = self().geometry().width(); + height = self().geometry().height(); + } else { + width = state().size.width(); + height = state().size.height(); + } + if(width <= 0 || height <= 0) return; - if(width != surfaceWidth || height != surfaceHeight) release(); - if(!surface) surface = new uint32_t[width * height]; + pixels.reallocate(width * height); - if(canvas.state.mode == Canvas::Mode::Color) { - nall::image image; - image.allocate(width, height); - image.fill(canvas.state.color.argb()); - memcpy(surface, image.data, image.size); + if(mode == Mode::Color) { + uint32_t color = state().color.value(); + for(auto& pixel : pixels) pixel = color; } - if(canvas.state.mode == Canvas::Mode::Gradient) { - nall::image image; - image.allocate(width, height); - image.gradient( - canvas.state.gradient[0].argb(), canvas.state.gradient[1].argb(), canvas.state.gradient[2].argb(), canvas.state.gradient[3].argb() + if(mode == Mode::Gradient) { + image fill; + fill.allocate(width, height); + fill.gradient( + state().gradient[0].value(), state().gradient[1].value(), + state().gradient[2].value(), state().gradient[3].value() ); - memcpy(surface, image.data, image.size); + memory::copy(pixels.data(), fill.data, fill.size); } - if(canvas.state.mode == Canvas::Mode::Image) { - nall::image image = canvas.state.image; - image.scale(width, height); - image.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); - memcpy(surface, image.data, image.size); + if(mode == Mode::Icon) { + auto icon = state().icon; + icon.scale(width, height); + icon.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); + memory::copy(pixels.data(), icon.data, icon.size); } - if(canvas.state.mode == Canvas::Mode::Data) { - if(width == canvas.state.width && height == canvas.state.height) { - memcpy(surface, canvas.state.data, width * height * sizeof(uint32_t)); - } else { - memset(surface, 0x00, width * height * sizeof(uint32_t)); - } + if(mode == Mode::Data) { + memory::copy( + pixels.data(), pixels.size() * sizeof(uint32_t), + state().data.data(), state().data.size() * sizeof(uint32_t) + ); } - - surfaceWidth = width; - surfaceHeight = height; } -void pCanvas::redraw() { +auto pCanvas::_redraw() -> void { InvalidateRect(hwnd, 0, false); } -void pCanvas::release() { - if(surface) { - delete[] surface; - surface = nullptr; - surfaceWidth = 0; - surfaceHeight = 0; - } } -} +#endif diff --git a/hiro/windows/widget/canvas.hpp b/hiro/windows/widget/canvas.hpp new file mode 100644 index 00000000..f66bb8be --- /dev/null +++ b/hiro/windows/widget/canvas.hpp @@ -0,0 +1,31 @@ +#if defined(Hiro_Canvas) + +namespace hiro { + +struct pCanvas : pWidget { + Declare(Canvas, Widget) + + auto minimumSize() const -> Size override; + auto setColor(Color color) -> void; + auto setData(Size size) -> void; + auto setDroppable(bool droppable) -> void; + auto setGeometry(Geometry geometry) -> void override; + auto setGradient(Color topLeft, Color topRight, Color bottomLeft, Color bottomRight) -> void; + auto setIcon(const image& icon) -> void; + auto update() -> void; + + enum class Mode : unsigned { Color, Data, Gradient, Icon }; + + auto _paint() -> void; + auto _rasterize() -> void; + auto _redraw() -> void; + + Mode mode = Mode::Color; + vector pixels; + signed width = 0; + signed height = 0; +}; + +} + +#endif diff --git a/hiro/windows/widget/check-button.cpp b/hiro/windows/widget/check-button.cpp index f39e3cdf..5590b028 100644 --- a/hiro/windows/widget/check-button.cpp +++ b/hiro/windows/widget/check-button.cpp @@ -1,84 +1,94 @@ -namespace phoenix { +#if defined(Hiro_CheckButton) -Size pCheckButton::minimumSize() { - Size size = pFont::size(hfont, checkButton.state.text); +namespace hiro { - if(checkButton.state.orientation == Orientation::Horizontal) { - size.width += checkButton.state.image.width; - size.height = max(checkButton.state.image.height, size.height); - } - - if(checkButton.state.orientation == Orientation::Vertical) { - size.width = max(checkButton.state.image.width, size.width); - size.height += checkButton.state.image.height; - } - - return {size.width + 20, size.height + 10}; -} - -void pCheckButton::setChecked(bool checked) { - SendMessage(hwnd, BM_SETCHECK, (WPARAM)checked, 0); -} - -void pCheckButton::setImage(const image& image, Orientation orientation) { - nall::image nallImage = image; - nallImage.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); - - if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } - if(himagelist) { ImageList_Destroy(himagelist); himagelist = 0; } - - if(OsVersion() < WindowsVista) nallImage.alphaBlend(GetSysColor(COLOR_BTNFACE)); - hbitmap = CreateBitmap(nallImage); - himagelist = ImageList_Create(nallImage.width, nallImage.height, ILC_COLOR32, 1, 0); - ImageList_Add(himagelist, hbitmap, NULL); - BUTTON_IMAGELIST list; - list.himl = himagelist; - switch(orientation) { - case Orientation::Horizontal: SetRect(&list.margin, 5, 0, 0, 0); list.uAlign = BUTTON_IMAGELIST_ALIGN_LEFT; break; - case Orientation::Vertical: SetRect(&list.margin, 0, 5, 0, 0); list.uAlign = BUTTON_IMAGELIST_ALIGN_TOP; break; - } - Button_SetImageList(hwnd, &list); - - setText(checkButton.state.text); -} - -void pCheckButton::setText(string text) { - if(text.empty()) { - SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) | BS_BITMAP); - } else { - SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) & ~BS_BITMAP); - } - - SetWindowText(hwnd, utf16_t(text)); -} - -void pCheckButton::constructor() { +auto pCheckButton::construct() -> void { hwnd = CreateWindow(L"BUTTON", L"", WS_CHILD | WS_TABSTOP | BS_CHECKBOX | BS_PUSHLIKE, - 0, 0, 0, 0, parentHwnd, (HMENU)id, GetModuleHandle(0), 0); - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&checkButton); - setDefaultFont(); - setChecked(checkButton.state.checked); - setImage(checkButton.state.image, checkButton.state.orientation); -//setText(checkButton.state.text); - synchronize(); + 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); + pWidget::_setState(); + _setState(); + setBordered(state().bordered); + setChecked(state().checked); } -void pCheckButton::destructor() { +auto pCheckButton::destruct() -> void { if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } if(himagelist) { ImageList_Destroy(himagelist); himagelist = 0; } DestroyWindow(hwnd); } -void pCheckButton::orphan() { - destructor(); - constructor(); +auto pCheckButton::minimumSize() -> Size { + auto size = pFont::size(hfont, state().text); + + if(state().orientation == Orientation::Horizontal) { + size.setWidth(size.width() + state().icon.width); + size.setHeight(max(size.height(), state().icon.height)); + } + + if(state().orientation == Orientation::Vertical) { + size.setWidth(max(size.width(), state().icon.width)); + size.setHeight(size.height() + state().icon.height); + } + + return {size.width() + (state().text ? 20 : 10), size.height() + 10}; } -void pCheckButton::onToggle() { - checkButton.state.checked = !checkButton.state.checked; - setChecked(checkButton.state.checked); - if(checkButton.onToggle) checkButton.onToggle(); +auto pCheckButton::setBordered(bool bordered) -> void { +} + +auto pCheckButton::setChecked(bool checked) -> void { + SendMessage(hwnd, BM_SETCHECK, (WPARAM)checked, 0); +} + +auto pCheckButton::setIcon(const image& icon) -> void { + _setState(); +} + +auto pCheckButton::setOrientation(Orientation orientation) -> void { + _setState(); +} + +auto pCheckButton::setText(const string& text) -> void { + _setState(); +} + +auto pCheckButton::onToggle() -> void { + state().checked = !state().checked; + setChecked(state().checked); + self().doToggle(); +} + +auto pCheckButton::_setState() -> void { + image icon = state().icon; + icon.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); + + if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } + if(himagelist) { ImageList_Destroy(himagelist); himagelist = 0; } + + if(OsVersion() < WindowsVista) icon.alphaBlend(GetSysColor(COLOR_BTNFACE)); + + hbitmap = CreateBitmap(icon); + himagelist = ImageList_Create(icon.width, icon.height, ILC_COLOR32, 1, 0); + ImageList_Add(himagelist, hbitmap, NULL); + BUTTON_IMAGELIST list; + list.himl = himagelist; + switch(state().orientation) { + case Orientation::Horizontal: SetRect(&list.margin, 5, 0, 0, 0); list.uAlign = BUTTON_IMAGELIST_ALIGN_LEFT; break; + case Orientation::Vertical: SetRect(&list.margin, 0, 5, 0, 0); list.uAlign = BUTTON_IMAGELIST_ALIGN_TOP; break; + } + Button_SetImageList(hwnd, &list); + + if(auto text = state().text) { + SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) &~ BS_BITMAP); + SetWindowText(hwnd, utf16_t(text)); + } else { + SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) | BS_BITMAP); + SetWindowText(hwnd, L""); + } } } + +#endif diff --git a/hiro/windows/widget/check-button.hpp b/hiro/windows/widget/check-button.hpp new file mode 100644 index 00000000..1b0f8ac5 --- /dev/null +++ b/hiro/windows/widget/check-button.hpp @@ -0,0 +1,25 @@ +#if defined(Hiro_CheckButton) + +namespace hiro { + +struct pCheckButton : pWidget { + Declare(CheckButton, Widget) + + auto minimumSize() -> Size; + auto setBordered(bool bordered) -> void; + auto setChecked(bool checked) -> void; + auto setIcon(const image& icon) -> void; + auto setOrientation(Orientation orientation) -> void; + auto setText(const string& text) -> void; + + auto onToggle() -> void; + + auto _setState() -> void; + + HBITMAP hbitmap = 0; + HIMAGELIST himagelist = 0; +}; + +} + +#endif diff --git a/hiro/windows/widget/check-label.cpp b/hiro/windows/widget/check-label.cpp index 35607b98..60fd9b36 100644 --- a/hiro/windows/widget/check-label.cpp +++ b/hiro/windows/widget/check-label.cpp @@ -1,44 +1,42 @@ -namespace phoenix { +#if defined(Hiro_CheckLabel) -Size pCheckLabel::minimumSize() { - Size size = pFont::size(hfont, checkLabel.state.text); - return {size.width + 20, size.height + 4}; -} +namespace hiro { -void pCheckLabel::setChecked(bool checked) { - SendMessage(hwnd, BM_SETCHECK, (WPARAM)checked, 0); -} - -void pCheckLabel::setText(string text) { - SetWindowText(hwnd, utf16_t(text)); -} - -void pCheckLabel::constructor() { +auto pCheckLabel::construct() -> void { hwnd = CreateWindow( L"BUTTON", L"", WS_CHILD | WS_TABSTOP | BS_CHECKBOX, - 0, 0, 0, 0, parentHwnd, (HMENU)id, GetModuleHandle(0), 0 + 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0 ); - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&checkLabel); - setDefaultFont(); - setChecked(checkLabel.state.checked); - setText(checkLabel.state.text); - synchronize(); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); + pWidget::_setState(); + setChecked(state().checked); + setText(state().text); } -void pCheckLabel::destructor() { +auto pCheckLabel::destruct() -> void { DestroyWindow(hwnd); } -void pCheckLabel::orphan() { - destructor(); - constructor(); +auto pCheckLabel::minimumSize() -> Size { + auto size = pFont::size(hfont, state().text); + return {size.width() + 20, size.height() + 4}; } -void pCheckLabel::onToggle() { - checkLabel.state.checked = !checkLabel.state.checked; - setChecked(checkLabel.state.checked); - if(checkLabel.onToggle) checkLabel.onToggle(); +auto pCheckLabel::setChecked(bool checked) -> void { + SendMessage(hwnd, BM_SETCHECK, (WPARAM)checked, 0); +} + +auto pCheckLabel::setText(const string& text) -> void { + SetWindowText(hwnd, utf16_t(text)); +} + +auto pCheckLabel::onToggle() -> void { + state().checked = !state().checked; + setChecked(state().checked); + self().doToggle(); } } + +#endif diff --git a/hiro/windows/widget/check-label.hpp b/hiro/windows/widget/check-label.hpp new file mode 100644 index 00000000..f9bad68b --- /dev/null +++ b/hiro/windows/widget/check-label.hpp @@ -0,0 +1,17 @@ +#if defined(Hiro_CheckLabel) + +namespace hiro { + +struct pCheckLabel : pWidget { + Declare(CheckLabel, Widget) + + auto minimumSize() -> Size; + auto setChecked(bool checked) -> void; + auto setText(const string& text) -> void; + + auto onToggle() -> void; +}; + +} + +#endif diff --git a/hiro/windows/widget/combo-button-item.cpp b/hiro/windows/widget/combo-button-item.cpp new file mode 100644 index 00000000..0b8b1a0e --- /dev/null +++ b/hiro/windows/widget/combo-button-item.cpp @@ -0,0 +1,42 @@ +#if defined(Hiro_ComboButton) + +namespace hiro { + +auto pComboButtonItem::construct() -> void { +} + +auto pComboButtonItem::destruct() -> void { +} + +auto pComboButtonItem::setIcon(const image& icon) -> void { + //unsupported +} + +auto pComboButtonItem::setSelected() -> void { + if(auto parent = _parent()) { + parent->lock(); + SendMessage(parent->hwnd, CB_SETCURSEL, self().offset(), 0); + parent->unlock(); + } +} + +auto pComboButtonItem::setText(const string& text) -> void { + if(auto parent = _parent()) { + parent->lock(); + SendMessage(parent->hwnd, CB_DELETESTRING, self().offset(), 0); + SendMessage(parent->hwnd, CB_INSERTSTRING, self().offset(), (LPARAM)(wchar_t*)utf16_t(state().text)); + if(state().selected) setSelected(); + parent->unlock(); + } +} + +auto pComboButtonItem::_parent() -> maybe { + if(auto parent = self().parentComboButton()) { + if(auto self = parent->self()) return *self; + } + return nothing; +} + +} + +#endif diff --git a/hiro/windows/widget/combo-button-item.hpp b/hiro/windows/widget/combo-button-item.hpp new file mode 100644 index 00000000..335e489a --- /dev/null +++ b/hiro/windows/widget/combo-button-item.hpp @@ -0,0 +1,17 @@ +#if defined(Hiro_ComboButton) + +namespace hiro { + +struct pComboButtonItem : pObject { + Declare(ComboButtonItem, Object) + + auto setIcon(const image& icon) -> void; + auto setSelected() -> void; + auto setText(const string& text) -> void; + + auto _parent() -> maybe; +}; + +} + +#endif diff --git a/hiro/windows/widget/combo-button.cpp b/hiro/windows/widget/combo-button.cpp index 48dbec46..65cefb69 100644 --- a/hiro/windows/widget/combo-button.cpp +++ b/hiro/windows/widget/combo-button.cpp @@ -1,77 +1,67 @@ -namespace phoenix { +#if defined(Hiro_ComboButton) -void pComboButton::append(string text) { - SendMessage(hwnd, CB_ADDSTRING, 0, (LPARAM)(wchar_t*)utf16_t(text)); - if(SendMessage(hwnd, CB_GETCOUNT, 0, 0) == 1) setSelection(0); -} +namespace hiro { -Size pComboButton::minimumSize() { - unsigned maximumWidth = 0; - for(auto& text : comboButton.state.text) maximumWidth = max(maximumWidth, pFont::size(hfont, text).width); - return {maximumWidth + 24, pFont::size(hfont, " ").height + 10}; -} - -void pComboButton::remove(unsigned selection) { - locked = true; - SendMessage(hwnd, CB_DELETESTRING, selection, 0); - locked = false; - - if(selection == comboButton.state.selection) comboButton.setSelection(0); -} - -void pComboButton::reset() { - SendMessage(hwnd, CB_RESETCONTENT, 0, 0); -} - -void pComboButton::setGeometry(Geometry geometry) { - //height = minimum drop-down list height; use CB_SETITEMHEIGHT to control actual widget height - pWidget::setGeometry({geometry.x, geometry.y, geometry.width, 1}); - RECT rc; - GetWindowRect(hwnd, &rc); - unsigned adjustedHeight = geometry.height - ((rc.bottom - rc.top) - SendMessage(hwnd, CB_GETITEMHEIGHT, (WPARAM)-1, 0)); - SendMessage(hwnd, CB_SETITEMHEIGHT, (WPARAM)-1, adjustedHeight); -} - -void pComboButton::setSelection(unsigned selection) { - SendMessage(hwnd, CB_SETCURSEL, selection, 0); -} - -void pComboButton::setText(unsigned selection, string text) { - locked = true; - SendMessage(hwnd, CB_DELETESTRING, selection, 0); - SendMessage(hwnd, CB_INSERTSTRING, selection, (LPARAM)(wchar_t*)utf16_t(text)); - setSelection(comboButton.state.selection); - locked = false; -} - -void pComboButton::constructor() { +auto pComboButton::construct() -> void { hwnd = CreateWindow( L"COMBOBOX", L"", WS_CHILD | WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS, 0, 0, 0, 0, - parentHwnd, (HMENU)id, GetModuleHandle(0), 0 + _parentHandle(), nullptr, GetModuleHandle(0), 0 ); - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&comboButton); - setDefaultFont(); - for(auto& text : comboButton.state.text) append(text); - setSelection(comboButton.state.selection); - synchronize(); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); + pWidget::_setState(); + for(auto& item : state().items) append(item); } -void pComboButton::destructor() { +auto pComboButton::destruct() -> void { DestroyWindow(hwnd); } -void pComboButton::orphan() { - destructor(); - constructor(); +auto pComboButton::append(sComboButtonItem item) -> void { + lock(); + SendMessage(hwnd, CB_ADDSTRING, 0, (LPARAM)(wchar_t*)utf16_t(item->state.text)); + if(item->state.selected) SendMessage(hwnd, CB_SETCURSEL, item->offset(), 0); + if(SendMessage(hwnd, CB_GETCURSEL, 0, 0) == CB_ERR) item->setSelected(); + unlock(); } -void pComboButton::onChange() { - unsigned selection = SendMessage(hwnd, CB_GETCURSEL, 0, 0); - if(selection == comboButton.state.selection) return; - comboButton.state.selection = selection; - if(comboButton.onChange) comboButton.onChange(); +auto pComboButton::minimumSize() const -> Size { + signed width = 0; + for(auto& item : state().items) { + width = max(width, pFont::size(hfont, item->state.text).width()); + } + return {width + 24, pFont::size(hfont, " ").height() + 10}; +} + +auto pComboButton::remove(sComboButtonItem item) -> void { + lock(); + SendMessage(hwnd, CB_DELETESTRING, item->offset(), 0); + if(item->state.selected) SendMessage(hwnd, CB_SETCURSEL, 0, 0); + unlock(); +} + +auto pComboButton::reset() -> void { + SendMessage(hwnd, CB_RESETCONTENT, 0, 0); +} + +auto pComboButton::setGeometry(Geometry geometry) -> void { + //height = minimum drop-down list height; use CB_SETITEMHEIGHT to control actual widget height + pWidget::setGeometry({geometry.x(), geometry.y(), geometry.width(), 1}); + RECT rc; + GetWindowRect(hwnd, &rc); + unsigned adjustedHeight = geometry.height() - ((rc.bottom - rc.top) - SendMessage(hwnd, CB_GETITEMHEIGHT, (WPARAM)-1, 0)); + SendMessage(hwnd, CB_SETITEMHEIGHT, (WPARAM)-1, adjustedHeight); +} + +auto pComboButton::onChange() -> void { + signed offset = SendMessage(hwnd, CB_GETCURSEL, 0, 0); + if(offset == CB_ERR) return; + for(auto& item : state().items) item->state.selected = false; + if(auto item = self().item(offset)) item->setSelected(); + self().doChange(); } } + +#endif diff --git a/hiro/windows/widget/combo-button.hpp b/hiro/windows/widget/combo-button.hpp new file mode 100644 index 00000000..c03e2348 --- /dev/null +++ b/hiro/windows/widget/combo-button.hpp @@ -0,0 +1,19 @@ +#if defined(Hiro_ComboButton) + +namespace hiro { + +struct pComboButton : pWidget { + Declare(ComboButton, Widget) + + auto append(sComboButtonItem item) -> void; + auto minimumSize() const -> Size override; + auto remove(sComboButtonItem item) -> void; + auto reset() -> void; + auto setGeometry(Geometry geometry) -> void override; + + auto onChange() -> void; +}; + +} + +#endif diff --git a/hiro/windows/widget/console.cpp b/hiro/windows/widget/console.cpp deleted file mode 100644 index 64abd98b..00000000 --- a/hiro/windows/widget/console.cpp +++ /dev/null @@ -1,56 +0,0 @@ -namespace phoenix { - -static LRESULT CALLBACK Console_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { - Console& console = *(Console*)GetWindowLongPtr(hwnd, GWLP_USERDATA); - if(msg == WM_CHAR) { - if(console.p.keyPress(wparam)) return 0; - } - return console.p.windowProc(hwnd, msg, wparam, lparam); -} - -void pConsole::print(string text) { -} - -void pConsole::reset() { -} - -void pConsole::setBackgroundColor(Color color) { - if(backgroundBrush) DeleteObject(backgroundBrush); - backgroundBrush = CreateSolidBrush(RGB(color.red, color.green, color.blue)); -} - -void pConsole::setForegroundColor(Color color) { -} - -void pConsole::setPrompt(string prompt) { -} - -void pConsole::constructor() { - hwnd = CreateWindowEx( - WS_EX_CLIENTEDGE, L"EDIT", L"", - WS_CHILD | WS_TABSTOP | ES_READONLY | ES_MULTILINE | ES_WANTRETURN, - 0, 0, 0, 0, parentHwnd, (HMENU)id, GetModuleHandle(0), 0 - ); - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&console); - setDefaultFont(); - setBackgroundColor(console.state.backgroundColor); - - windowProc = (LRESULT CALLBACK (*)(HWND, UINT, LPARAM, WPARAM))GetWindowLongPtr(hwnd, GWLP_WNDPROC); - SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)Console_windowProc); - synchronize(); -} - -void pConsole::destructor() { - DestroyWindow(hwnd); -} - -void pConsole::orphan() { - destructor(); - constructor(); -} - -bool pConsole::keyPress(unsigned scancode) { - return false; -} - -} diff --git a/hiro/windows/widget/frame.cpp b/hiro/windows/widget/frame.cpp index cb30c34a..9e5a961d 100644 --- a/hiro/windows/widget/frame.cpp +++ b/hiro/windows/widget/frame.cpp @@ -1,50 +1,54 @@ -namespace phoenix { +#if defined(Hiro_Frame) -void pFrame::setEnabled(bool enabled) { - if(frame.state.layout) frame.state.layout->setEnabled(frame.state.layout->enabled()); - pWidget::setEnabled(enabled); -} +namespace hiro { -void pFrame::setGeometry(Geometry geometry) { - bool empty = frame.state.text.empty(); - Size size = pFont::size(hfont, frame.state.text); - pWidget::setGeometry({ - geometry.x, geometry.y - (empty ? size.height >> 1 : 0), - geometry.width, geometry.height + (empty ? size.height >> 1 : 0) - }); - if(frame.state.layout == nullptr) return; - if(empty) size.height = 1; - geometry.x += 1, geometry.width -= 2; - geometry.y += size.height, geometry.height -= size.height + 2; - frame.state.layout->setGeometry(geometry); -} - -void pFrame::setText(string text) { - SetWindowText(hwnd, utf16_t(text)); -} - -void pFrame::setVisible(bool visible) { - if(frame.state.layout) frame.state.layout->setVisible(frame.state.layout->visible()); - pWidget::setVisible(visible); -} - -void pFrame::constructor() { +auto pFrame::construct() -> void { hwnd = CreateWindow(L"BUTTON", L"", WS_CHILD | BS_GROUPBOX, - 0, 0, 0, 0, parentHwnd, (HMENU)id, GetModuleHandle(0), 0); - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&frame); - setDefaultFont(); - setText(frame.state.text); - synchronize(); + 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); + pWidget::_setState(); + setText(state().text); } -void pFrame::destructor() { +auto pFrame::destruct() -> void { DestroyWindow(hwnd); } -void pFrame::orphan() { - destructor(); - constructor(); +auto pFrame::setEnabled(bool enabled) -> void { + if(auto layout = state().layout) layout->setEnabled(layout->enabled()); + pWidget::setEnabled(enabled); +} + +auto pFrame::setGeometry(Geometry geometry) -> void { + bool empty = !state().text; + auto size = pFont::size(hfont, state().text); + pWidget::setGeometry({ + geometry.x(), + geometry.y() - (empty ? size.height() >> 1 : 0), + geometry.width(), + geometry.height() + (empty ? size.height() >> 1 : 0) + }); + if(auto layout = state().layout) { + if(empty) size.setHeight(1); + layout->setGeometry({ + geometry.x() + 1, + geometry.y() + size.height(), + geometry.width() - 2, + geometry.height() - (size.height() + 2) + }); + } +} + +auto pFrame::setText(const string& text) -> void { + SetWindowText(hwnd, utf16_t(text)); +} + +auto pFrame::setVisible(bool visible) -> void { + if(auto layout = state().layout) layout->setVisible(layout->visible()); + pWidget::setVisible(visible); } } + +#endif diff --git a/hiro/windows/widget/frame.hpp b/hiro/windows/widget/frame.hpp new file mode 100644 index 00000000..32e2c92f --- /dev/null +++ b/hiro/windows/widget/frame.hpp @@ -0,0 +1,16 @@ +#if defined(Hiro_Frame) + +namespace hiro { + +struct pFrame : pWidget { + Declare(Frame, Widget) + + auto setEnabled(bool enabled) -> void override; + auto setGeometry(Geometry geometry) -> void override; + auto setText(const string& text) -> void; + auto setVisible(bool visible) -> void override; +}; + +} + +#endif diff --git a/hiro/windows/widget/hex-edit.cpp b/hiro/windows/widget/hex-edit.cpp index b29f81b4..6a8f7880 100644 --- a/hiro/windows/widget/hex-edit.cpp +++ b/hiro/windows/widget/hex-edit.cpp @@ -1,30 +1,35 @@ -namespace phoenix { +#if defined(Hiro_HexEdit) -static LRESULT CALLBACK HexEdit_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { - HexEdit& hexEdit = *(HexEdit*)GetWindowLongPtr(hwnd, GWLP_USERDATA); +namespace hiro { + +static auto CALLBACK HexEdit_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT { + auto object = (mObject*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if(!object) return DefWindowProc(hwnd, msg, wparam, lparam); + auto hexEdit = dynamic_cast(object); + if(!hexEdit) return DefWindowProc(hwnd, msg, wparam, lparam); + auto self = hexEdit->self(); + if(!self) return DefWindowProc(hwnd, msg, wparam, lparam); switch(msg) { case WM_KEYDOWN: - if(hexEdit.p.keyPress(wparam)) return 0; + if(self->keyPress(wparam)) return 0; break; case WM_MOUSEWHEEL: { signed offset = -((int16_t)HIWORD(wparam) / WHEEL_DELTA); - hexEdit.p.scrollTo(hexEdit.p.scrollPosition() + offset); + self->scrollTo(self->scrollPosition() + offset); return true; } case WM_SIZE: { RECT rc; - GetClientRect(hexEdit.p.hwnd, &rc); - SetWindowPos(hexEdit.p.scrollBar, HWND_TOP, rc.right - 18, 0, 18, rc.bottom, SWP_SHOWWINDOW); + GetClientRect(self->hwnd, &rc); + SetWindowPos(self->scrollBar, HWND_TOP, rc.right - 18, 0, 18, rc.bottom, SWP_SHOWWINDOW); break; } case WM_VSCROLL: { - SCROLLINFO info; - memset(&info, 0, sizeof(SCROLLINFO)); - info.cbSize = sizeof(SCROLLINFO); + SCROLLINFO info{sizeof(SCROLLINFO)}; info.fMask = SIF_ALL; GetScrollInfo((HWND)lparam, SB_CTL, &info); switch(LOWORD(wparam)) { @@ -41,43 +46,73 @@ static LRESULT CALLBACK HexEdit_windowProc(HWND hwnd, UINT msg, WPARAM wparam, L SetScrollInfo((HWND)lparam, SB_CTL, &info, TRUE); GetScrollInfo((HWND)lparam, SB_CTL, &info); //get clamped position - hexEdit.p.scrollTo(info.nPos); - return TRUE; + self->scrollTo(info.nPos); + return true; } } - return hexEdit.p.windowProc(hwnd, msg, wparam, lparam); + return self->windowProc(hwnd, msg, wparam, lparam); } -void pHexEdit::setBackgroundColor(Color color) { +auto pHexEdit::construct() -> void { + hwnd = CreateWindowEx( + WS_EX_CLIENTEDGE, L"EDIT", L"", + WS_CHILD | WS_TABSTOP | ES_AUTOHSCROLL | ES_READONLY | ES_MULTILINE | ES_WANTRETURN, + 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); + + scrollBar = CreateWindowEx( + 0, L"SCROLLBAR", L"", + WS_VISIBLE | WS_CHILD | SBS_VERT, + 0, 0, 0, 0, hwnd, nullptr, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(scrollBar, GWLP_USERDATA, (LONG_PTR)&reference); + + windowProc = (WindowProc)GetWindowLongPtr(hwnd, GWLP_WNDPROC); + SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)HexEdit_windowProc); + + pWidget::_setState(); + setBackgroundColor(state().backgroundColor); + setLength(state().length); + setOffset(state().offset); + update(); + PostMessage(hwnd, EM_SETSEL, 10, 10); +} + +auto pHexEdit::destruct() -> void { + DestroyWindow(hwnd); +} + +auto pHexEdit::setBackgroundColor(Color color) -> void { if(backgroundBrush) DeleteObject(backgroundBrush); - backgroundBrush = CreateSolidBrush(RGB(color.red, color.green, color.blue)); + backgroundBrush = CreateSolidBrush(color ? CreateRGB(color) : GetSysColor(COLOR_WINDOW)); } -void pHexEdit::setColumns(unsigned columns) { +auto pHexEdit::setColumns(unsigned columns) -> void { update(); } -void pHexEdit::setForegroundColor(Color color) { +auto pHexEdit::setForegroundColor(Color color) -> void { } -void pHexEdit::setLength(unsigned length) { - SetScrollRange(scrollBar, SB_CTL, 0, rowsScrollable(), TRUE); +auto pHexEdit::setLength(unsigned length) -> void { + SetScrollRange(scrollBar, SB_CTL, 0, rowsScrollable(), true); EnableWindow(scrollBar, rowsScrollable() > 0); update(); } -void pHexEdit::setOffset(unsigned offset) { - SetScrollPos(scrollBar, SB_CTL, offset / hexEdit.state.columns, TRUE); +auto pHexEdit::setOffset(unsigned offset) -> void { + SetScrollPos(scrollBar, SB_CTL, offset / state().columns, true); update(); } -void pHexEdit::setRows(unsigned rows) { +auto pHexEdit::setRows(unsigned rows) -> void { update(); } -void pHexEdit::update() { - if(!hexEdit.onRead) { +auto pHexEdit::update() -> void { + if(!state().onRead) { SetWindowText(hwnd, L""); return; } @@ -85,16 +120,16 @@ void pHexEdit::update() { unsigned cursorPosition = Edit_GetSel(hwnd); string output; - unsigned offset = hexEdit.state.offset; - for(unsigned row = 0; row < hexEdit.state.rows; row++) { + unsigned offset = state().offset; + for(auto row : range(state().rows)) { output.append(hex<8>(offset)); output.append(" "); string hexdata; string ansidata = " "; - for(unsigned column = 0; column < hexEdit.state.columns; column++) { - if(offset < hexEdit.state.length) { - uint8_t data = hexEdit.onRead(offset++); + for(auto column : range(state().columns)) { + if(offset < state().length) { + uint8_t data = self().doRead(offset++); hexdata.append(hex<2>(data)); hexdata.append(" "); ansidata.append(data >= 0x20 && data <= 0x7e ? (char)data : '.'); @@ -106,56 +141,19 @@ void pHexEdit::update() { output.append(hexdata); output.append(ansidata); - if(offset >= hexEdit.state.length) break; - if(row != hexEdit.state.rows - 1) output.append("\r\n"); + if(offset >= state().length) break; + if(row != state().rows - 1) output.append("\r\n"); } SetWindowText(hwnd, utf16_t(output)); Edit_SetSel(hwnd, LOWORD(cursorPosition), HIWORD(cursorPosition)); } -void pHexEdit::constructor() { - hwnd = CreateWindowEx( - WS_EX_CLIENTEDGE, L"EDIT", L"", - WS_CHILD | WS_TABSTOP | ES_AUTOHSCROLL | ES_READONLY | ES_MULTILINE | ES_WANTRETURN, - 0, 0, 0, 0, parentHwnd, (HMENU)id, GetModuleHandle(0), 0 - ); - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&hexEdit); - - scrollBar = CreateWindowEx( - 0, L"SCROLLBAR", L"", - WS_VISIBLE | WS_CHILD | SBS_VERT, - 0, 0, 0, 0, hwnd, (HMENU)id, GetModuleHandle(0), 0 - ); - SetWindowLongPtr(scrollBar, GWLP_USERDATA, (LONG_PTR)&hexEdit); - - windowProc = (WindowProc)GetWindowLongPtr(hwnd, GWLP_WNDPROC); - SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)HexEdit_windowProc); - - setDefaultFont(); - setBackgroundColor(hexEdit.state.backgroundColor); - setLength(hexEdit.state.length); - setOffset(hexEdit.state.offset); - update(); - PostMessage(hwnd, EM_SETSEL, 10, 10); - - synchronize(); -} - -void pHexEdit::destructor() { - DestroyWindow(hwnd); -} - -void pHexEdit::orphan() { - destructor(); - constructor(); -} - bool pHexEdit::keyPress(unsigned scancode) { - if(!hexEdit.onRead) return false; + if(!state().onRead) return false; signed position = LOWORD(Edit_GetSel(hwnd)); - signed lineWidth = 10 + (hexEdit.state.columns * 3) + 1 + hexEdit.state.columns + 2; + signed lineWidth = 10 + (state().columns * 3) + 1 + state().columns + 2; signed cursorY = position / lineWidth; signed cursorX = position % lineWidth; @@ -179,18 +177,18 @@ bool pHexEdit::keyPress(unsigned scancode) { if(scancode == VK_DOWN) { if(cursorY >= rows() - 1) return true; - if(cursorY < hexEdit.state.rows - 1) return false; + if(cursorY < state().rows - 1) return false; scrollTo(scrollPosition() + 1); return true; } if(scancode == VK_PRIOR) { - scrollTo(scrollPosition() - hexEdit.state.rows); + scrollTo(scrollPosition() - state().rows); return true; } if(scancode == VK_NEXT) { - scrollTo(scrollPosition() + hexEdit.state.rows); + scrollTo(scrollPosition() + state().rows); return true; } @@ -207,12 +205,12 @@ bool pHexEdit::keyPress(unsigned scancode) { //not on a space bool cursorNibble = (cursorX % 3) == 1; //0 = high, 1 = low cursorX /= 3; - if(cursorX < hexEdit.state.columns) { + if(cursorX < state().columns) { //not in ANSI region - unsigned offset = hexEdit.state.offset + (cursorY * hexEdit.state.columns + cursorX); + unsigned offset = state().offset + (cursorY * state().columns + cursorX); - if(offset >= hexEdit.state.length) return false; //do not edit past end of data - uint8_t data = hexEdit.onRead(offset); + if(offset >= state().length) return false; //do not edit past end of data + uint8_t data = self().doRead(offset); //write modified value if(cursorNibble == 1) { @@ -220,11 +218,11 @@ bool pHexEdit::keyPress(unsigned scancode) { } else { data = (data & 0x0f) | (scancode << 4); } - if(hexEdit.onWrite) hexEdit.onWrite(offset, data); + self().doWrite(offset, data); //auto-advance cursor to next nibble or byte position++; - if(cursorNibble && cursorX != hexEdit.state.columns - 1) position++; + if(cursorNibble && cursorX != state().columns - 1) position++; Edit_SetSel(hwnd, position, position); //refresh output to reflect modified data @@ -237,22 +235,24 @@ bool pHexEdit::keyPress(unsigned scancode) { } signed pHexEdit::rows() { - return (max(1u, hexEdit.state.length) + hexEdit.state.columns - 1) / hexEdit.state.columns; + return (max(1u, state().length) + state().columns - 1) / state().columns; } signed pHexEdit::rowsScrollable() { - return max(0u, rows() - hexEdit.state.rows); + return max(0u, rows() - state().rows); } signed pHexEdit::scrollPosition() { - return hexEdit.state.offset / hexEdit.state.columns; + return state().offset / state().columns; } void pHexEdit::scrollTo(signed position) { if(position > rowsScrollable()) position = rowsScrollable(); if(position < 0) position = 0; if(position == scrollPosition()) return; - hexEdit.setOffset(position * hexEdit.state.columns); + self().setOffset(position * state().columns); } } + +#endif diff --git a/hiro/windows/widget/hex-edit.hpp b/hiro/windows/widget/hex-edit.hpp new file mode 100644 index 00000000..cd8ff7e6 --- /dev/null +++ b/hiro/windows/widget/hex-edit.hpp @@ -0,0 +1,29 @@ +#if defined(Hiro_HexEdit) + +namespace hiro { + +struct pHexEdit : pWidget { + Declare(HexEdit, Widget) + + auto setBackgroundColor(Color color) -> void; + auto setColumns(unsigned columns) -> void; + auto setForegroundColor(Color color) -> void; + auto setLength(unsigned length) -> void; + auto setOffset(unsigned offset) -> void; + auto setRows(unsigned rows) -> void; + auto update() -> void; + + auto keyPress(unsigned key) -> bool; + auto rows() -> signed; + auto rowsScrollable() -> signed; + auto scrollPosition() -> signed; + auto scrollTo(signed position) -> void; + + WindowProc windowProc = nullptr; + HWND scrollBar = nullptr; + HBRUSH backgroundBrush = nullptr; +}; + +} + +#endif diff --git a/hiro/windows/widget/horizontal-scroller.cpp b/hiro/windows/widget/horizontal-scroller.cpp index fe6e5a15..93ece5f1 100644 --- a/hiro/windows/widget/horizontal-scroller.cpp +++ b/hiro/windows/widget/horizontal-scroller.cpp @@ -1,45 +1,42 @@ -namespace phoenix { +#if defined(Hiro_HorizontalScroller) -Size pHorizontalScroller::minimumSize() { - return {0, 18}; -} +namespace hiro { -void pHorizontalScroller::setLength(unsigned length) { - length += (length == 0); - SetScrollRange(hwnd, SB_CTL, 0, length - 1, TRUE); - horizontalScroller.setPosition(0); -} - -void pHorizontalScroller::setPosition(unsigned position) { - SetScrollPos(hwnd, SB_CTL, position, TRUE); -} - -void pHorizontalScroller::constructor() { +auto pHorizontalScroller::construct() -> void { hwnd = CreateWindow( L"SCROLLBAR", L"", WS_CHILD | WS_TABSTOP | SBS_HORZ, - 0, 0, 0, 0, parentHwnd, (HMENU)id, GetModuleHandle(0), 0 + 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0 ); - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&horizontalScroller); - unsigned position = horizontalScroller.state.position; - setLength(horizontalScroller.state.length); - horizontalScroller.setPosition(position); - synchronize(); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); + pWidget::_setState(); + setLength(state().length); + setPosition(state().position); } -void pHorizontalScroller::destructor() { +auto pHorizontalScroller::destruct() -> void { DestroyWindow(hwnd); } -void pHorizontalScroller::orphan() { - destructor(); - constructor(); +auto pHorizontalScroller::minimumSize() const -> Size { + return {0, 18}; } -void pHorizontalScroller::onChange(WPARAM wparam) { +auto pHorizontalScroller::setLength(unsigned length) -> void { + length += (length == 0); + SetScrollRange(hwnd, SB_CTL, 0, length - 1, TRUE); +} + +auto pHorizontalScroller::setPosition(unsigned position) -> void { + SetScrollPos(hwnd, SB_CTL, position, TRUE); +} + +auto pHorizontalScroller::onChange(WPARAM wparam) -> void { unsigned position = ScrollEvent(hwnd, wparam); - if(position == horizontalScroller.state.position) return; - horizontalScroller.state.position = position; - if(horizontalScroller.onChange) horizontalScroller.onChange(); + if(position == state().position) return; + state().position = position; + self().doChange(); } } + +#endif diff --git a/hiro/windows/widget/horizontal-scroller.hpp b/hiro/windows/widget/horizontal-scroller.hpp new file mode 100644 index 00000000..50312e71 --- /dev/null +++ b/hiro/windows/widget/horizontal-scroller.hpp @@ -0,0 +1,17 @@ +#if defined(Hiro_HorizontalScroller) + +namespace hiro { + +struct pHorizontalScroller : pWidget { + Declare(HorizontalScroller, Widget) + + auto minimumSize() const -> Size override; + auto setLength(unsigned length) -> void; + auto setPosition(unsigned position) -> void; + + auto onChange(WPARAM wparam) -> void; +}; + +} + +#endif diff --git a/hiro/windows/widget/horizontal-slider.cpp b/hiro/windows/widget/horizontal-slider.cpp index 310047f6..605104ef 100644 --- a/hiro/windows/widget/horizontal-slider.cpp +++ b/hiro/windows/widget/horizontal-slider.cpp @@ -1,47 +1,43 @@ -namespace phoenix { +#if defined(Hiro_HorizontalSlider) -Size pHorizontalSlider::minimumSize() { - return {0, 25}; -} +namespace hiro { -void pHorizontalSlider::setLength(unsigned length) { - length += (length == 0); - SendMessage(hwnd, TBM_SETRANGE, (WPARAM)true, (LPARAM)MAKELONG(0, length - 1)); - SendMessage(hwnd, TBM_SETPAGESIZE, 0, (LPARAM)(length >> 3)); - horizontalSlider.setPosition(0); -} - -void pHorizontalSlider::setPosition(unsigned position) { - SendMessage(hwnd, TBM_SETPOS, (WPARAM)true, (LPARAM)position); -} - -void pHorizontalSlider::constructor() { +auto pHorizontalSlider::construct() -> void { hwnd = CreateWindow( TRACKBAR_CLASS, L"", WS_CHILD | WS_TABSTOP | TBS_TRANSPARENTBKGND | TBS_NOTICKS | TBS_BOTH | TBS_HORZ, - 0, 0, 0, 0, parentHwnd, (HMENU)id, GetModuleHandle(0), 0 + 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0 ); - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&horizontalSlider); - - unsigned position = horizontalSlider.state.position; - setLength(horizontalSlider.state.length); - horizontalSlider.setPosition(position); - synchronize(); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); + pWidget::_setState(); + setLength(state().length); + setPosition(state().position); } -void pHorizontalSlider::destructor() { +auto pHorizontalSlider::destruct() -> void { DestroyWindow(hwnd); } -void pHorizontalSlider::orphan() { - destructor(); - constructor(); +auto pHorizontalSlider::minimumSize() const -> Size { + return {0, 25}; } -void pHorizontalSlider::onChange() { +auto pHorizontalSlider::setLength(unsigned length) -> void { + length += (length == 0); + SendMessage(hwnd, TBM_SETRANGE, (WPARAM)true, (LPARAM)MAKELONG(0, length - 1)); + SendMessage(hwnd, TBM_SETPAGESIZE, 0, (LPARAM)(length >> 3)); +} + +auto pHorizontalSlider::setPosition(unsigned position) -> void { + SendMessage(hwnd, TBM_SETPOS, (WPARAM)true, (LPARAM)position); +} + +auto pHorizontalSlider::onChange() -> void { unsigned position = SendMessage(hwnd, TBM_GETPOS, 0, 0); - if(position == horizontalSlider.state.position) return; - horizontalSlider.state.position = position; - if(horizontalSlider.onChange) horizontalSlider.onChange(); + if(position == state().position) return; + state().position = position; + self().doChange(); } } + +#endif diff --git a/hiro/windows/widget/horizontal-slider.hpp b/hiro/windows/widget/horizontal-slider.hpp new file mode 100644 index 00000000..6a81777b --- /dev/null +++ b/hiro/windows/widget/horizontal-slider.hpp @@ -0,0 +1,17 @@ +#if defined(Hiro_HorizontalSlider) + +namespace hiro { + +struct pHorizontalSlider : pWidget { + Declare(HorizontalSlider, Widget) + + auto minimumSize() const -> Size override; + auto setLength(unsigned length) -> void; + auto setPosition(unsigned position) -> void; + + auto onChange() -> void; +}; + +} + +#endif diff --git a/hiro/windows/widget/label.cpp b/hiro/windows/widget/label.cpp index d2345bdc..19709d0f 100644 --- a/hiro/windows/widget/label.cpp +++ b/hiro/windows/widget/label.cpp @@ -1,44 +1,45 @@ -namespace phoenix { +#if defined(Hiro_Label) -Size pLabel::minimumSize() { - Size size = pFont::size(hfont, label.state.text); - return {size.width, size.height}; +namespace hiro { + +auto pLabel::construct() -> void { + hwnd = CreateWindow(L"hiroLabel", L"", + WS_CHILD, + 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); + pWidget::_setState(); + setText(state().text); } -void pLabel::setText(string text) { +auto pLabel::destruct() -> void { + DestroyWindow(hwnd); +} + +auto pLabel::minimumSize() const -> Size { + Size size = pFont::size(hfont, state().text); + return {size.width(), size.height()}; +} + +auto pLabel::setHorizontalAlignment(double alignment) -> void { +} + +auto pLabel::setText(const string& text) -> void { SetWindowText(hwnd, utf16_t(text)); InvalidateRect(hwnd, 0, false); } -void pLabel::constructor() { - hwnd = CreateWindow(L"phoenix_label", L"", - WS_CHILD, - 0, 0, 0, 0, parentHwnd, (HMENU)id, GetModuleHandle(0), 0); - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&label); - setDefaultFont(); - setText(label.state.text); - synchronize(); +auto pLabel::setVerticalAlignment(double alignment) -> void { } -void pLabel::destructor() { - DestroyWindow(hwnd); -} - -void pLabel::orphan() { - destructor(); - constructor(); -} - -static LRESULT CALLBACK Label_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { - HWND parentHwnd = GetParent(hwnd); - Label* label = (Label*)GetWindowLongPtr(hwnd, GWLP_USERDATA); - if(label == nullptr) return DefWindowProc(hwnd, msg, wparam, lparam); - Window* window = (Window*)label->Sizable::state.window; - if(window == nullptr) return DefWindowProc(hwnd, msg, wparam, lparam); +static auto CALLBACK Label_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT { + auto label = (mLabel*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if(!label) return DefWindowProc(hwnd, msg, wparam, lparam); + auto window = label->parentWindow(true); + if(!window) return DefWindowProc(hwnd, msg, wparam, lparam); switch(msg) { case WM_GETDLGCODE: return DLGC_STATIC | DLGC_WANTCHARS; - case WM_ERASEBKGND: return TRUE; + case WM_ERASEBKGND: return true; case WM_PAINT: { PAINTSTRUCT ps; BeginPaint(hwnd, &ps); @@ -46,7 +47,7 @@ static LRESULT CALLBACK Label_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPA GetClientRect(hwnd, &rc); DrawThemeParentBackground(hwnd, ps.hdc, &rc); SetBkMode(ps.hdc, TRANSPARENT); - SelectObject(ps.hdc, ((Widget*)label)->p.hfont); + SelectObject(ps.hdc, label->self()->hfont); unsigned length = GetWindowTextLength(hwnd); wchar_t text[length + 1]; GetWindowText(hwnd, text, length + 1); @@ -58,7 +59,7 @@ static LRESULT CALLBACK Label_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPA rc.bottom = rc.top + height; DrawText(ps.hdc, text, -1, &rc, DT_LEFT | DT_END_ELLIPSIS); EndPaint(hwnd, &ps); - return FALSE; + return false; } } @@ -66,3 +67,5 @@ static LRESULT CALLBACK Label_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPA } } + +#endif diff --git a/hiro/windows/widget/label.hpp b/hiro/windows/widget/label.hpp new file mode 100644 index 00000000..60d09c4f --- /dev/null +++ b/hiro/windows/widget/label.hpp @@ -0,0 +1,16 @@ +#if defined(Hiro_Label) + +namespace hiro { + +struct pLabel : pWidget { + Declare(Label, Widget) + + auto minimumSize() const -> Size override; + auto setHorizontalAlignment(double alignment) -> void; + auto setText(const string& text) -> void; + auto setVerticalAlignment(double alignment) -> void; +}; + +} + +#endif diff --git a/hiro/windows/widget/line-edit.cpp b/hiro/windows/widget/line-edit.cpp index c1e0be14..5dd03e13 100644 --- a/hiro/windows/widget/line-edit.cpp +++ b/hiro/windows/widget/line-edit.cpp @@ -1,29 +1,54 @@ -namespace phoenix { +#if defined(Hiro_LineEdit) -Size pLineEdit::minimumSize() { - Size size = pFont::size(hfont, lineEdit.state.text); - return {size.width + 12, size.height + 10}; +namespace hiro { + +auto pLineEdit::construct() -> void { + hwnd = CreateWindowEx( + WS_EX_CLIENTEDGE, L"EDIT", L"", + WS_CHILD | WS_TABSTOP | ES_AUTOHSCROLL | ES_AUTOVSCROLL, + 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); + pWidget::_setState(); + setBackgroundColor(state().backgroundColor); + setEditable(state().editable); + setText(state().text); } -void pLineEdit::setBackgroundColor(Color color) { - if(backgroundBrush) DeleteObject(backgroundBrush); - backgroundBrush = CreateSolidBrush(RGB(color.red, color.green, color.blue)); +auto pLineEdit::destruct() -> void { + if(backgroundBrush) { DeleteObject(backgroundBrush); backgroundBrush = 0; } + DestroyWindow(hwnd); } -void pLineEdit::setEditable(bool editable) { +auto pLineEdit::minimumSize() const -> Size { + auto size = pFont::size(hfont, state().text); + return {size.width() + 12, size.height() + 10}; +} + +auto pLineEdit::setBackgroundColor(Color color) -> void { + if(backgroundBrush) { DeleteObject(backgroundBrush); backgroundBrush = 0; } + backgroundBrush = CreateSolidBrush(color ? CreateRGB(color) : GetSysColor(COLOR_WINDOW)); +} + +auto pLineEdit::setEditable(bool editable) -> void { SendMessage(hwnd, EM_SETREADONLY, editable == false, 0); } -void pLineEdit::setForegroundColor(Color color) { +auto pLineEdit::setForegroundColor(Color color) -> void { } -void pLineEdit::setText(string text) { - locked = true; +auto pLineEdit::setText(const string& text) -> void { + lock(); SetWindowText(hwnd, utf16_t(text)); - locked = false; + unlock(); } -string pLineEdit::text() { +auto pLineEdit::onChange() -> void { + state().text = _text(); + if(!locked()) self().doChange(); +} + +auto pLineEdit::_text() -> string { unsigned length = GetWindowTextLength(hwnd); wchar_t text[length + 1]; GetWindowText(hwnd, text, length + 1); @@ -31,33 +56,6 @@ string pLineEdit::text() { return (const char*)utf8_t(text); } -void pLineEdit::constructor() { - hwnd = CreateWindowEx( - WS_EX_CLIENTEDGE, L"EDIT", L"", - WS_CHILD | WS_TABSTOP | ES_AUTOHSCROLL | ES_AUTOVSCROLL, - 0, 0, 0, 0, parentHwnd, (HMENU)id, GetModuleHandle(0), 0 - ); - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&lineEdit); - setDefaultFont(); - setBackgroundColor(lineEdit.state.backgroundColor); - setEditable(lineEdit.state.editable); - setText(lineEdit.state.text); - synchronize(); } -void pLineEdit::destructor() { - lineEdit.state.text = text(); - DestroyWindow(hwnd); -} - -void pLineEdit::orphan() { - destructor(); - constructor(); -} - -void pLineEdit::onChange() { - if(locked) return; - if(lineEdit.onChange) lineEdit.onChange(); -} - -} +#endif diff --git a/hiro/windows/widget/line-edit.hpp b/hiro/windows/widget/line-edit.hpp new file mode 100644 index 00000000..577028cd --- /dev/null +++ b/hiro/windows/widget/line-edit.hpp @@ -0,0 +1,23 @@ +#if defined(Hiro_LineEdit) + +namespace hiro { + +struct pLineEdit : pWidget { + Declare(LineEdit, Widget) + + auto minimumSize() const -> Size; + auto setBackgroundColor(Color color) -> void; + auto setEditable(bool editable) -> void; + auto setForegroundColor(Color color) -> void; + auto setText(const string& text) -> void; + + auto onChange() -> void; + + auto _text() -> string; + + HBRUSH backgroundBrush = nullptr; +}; + +} + +#endif diff --git a/hiro/windows/widget/list-view-cell.cpp b/hiro/windows/widget/list-view-cell.cpp new file mode 100644 index 00000000..90a41184 --- /dev/null +++ b/hiro/windows/widget/list-view-cell.cpp @@ -0,0 +1,49 @@ +#if defined(Hiro_ListView) + +namespace hiro { + +auto pListViewCell::construct() -> void { +} + +auto pListViewCell::destruct() -> void { +} + +auto pListViewCell::setBackgroundColor(Color color) -> void { +} + +auto pListViewCell::setForegroundColor(Color color) -> void { +} + +auto pListViewCell::setIcon(const image& icon) -> void { +} + +auto pListViewCell::setText(const string& text) -> void { +} + +auto pListViewCell::_parent() -> maybe { + if(auto parent = self().parentListViewItem()) { + if(auto self = parent->self()) return *self; + } + return nothing; +} + +auto pListViewCell::_setState() -> void { + if(auto item = _parent()) { + if(auto parent = item->_parent()) { + parent->lock(); + wchar_t text[] = L""; + LVITEM lvItem; + lvItem.mask = LVIF_TEXT | LVIF_IMAGE; + lvItem.iItem = item->self().offset(); + lvItem.iSubItem = self().offset(); + lvItem.iImage = parent->self().columns(); + lvItem.pszText = text; + ListView_SetItem(parent->hwnd, &lvItem); + parent->unlock(); + } + } +} + +} + +#endif diff --git a/hiro/windows/widget/list-view-cell.hpp b/hiro/windows/widget/list-view-cell.hpp new file mode 100644 index 00000000..3f4f0a6e --- /dev/null +++ b/hiro/windows/widget/list-view-cell.hpp @@ -0,0 +1,19 @@ +#if defined(Hiro_ListView) + +namespace hiro { + +struct pListViewCell : pObject { + Declare(ListViewCell, Object) + + auto setBackgroundColor(Color color) -> void; + auto setForegroundColor(Color color) -> void; + auto setIcon(const image& icon) -> void; + auto setText(const string& text) -> void; + + auto _parent() -> maybe; + auto _setState() -> void; +}; + +} + +#endif diff --git a/hiro/windows/widget/list-view-column.cpp b/hiro/windows/widget/list-view-column.cpp new file mode 100644 index 00000000..86bcdee2 --- /dev/null +++ b/hiro/windows/widget/list-view-column.cpp @@ -0,0 +1,81 @@ +#if defined(Hiro_ListView) + +namespace hiro { + +auto pListViewColumn::construct() -> void { +} + +auto pListViewColumn::destruct() -> void { +} + +auto pListViewColumn::setActive() -> void { + //unsupported +} + +auto pListViewColumn::setBackgroundColor(Color color) -> void { +} + +auto pListViewColumn::setEditable(bool editable) -> void { + //unsupported +} + +auto pListViewColumn::setExpandable(bool expandable) -> void { +} + +auto pListViewColumn::setForegroundColor(Color color) -> void { +} + +auto pListViewColumn::setHorizontalAlignment(double alignment) -> void { + _setState(); +} + +auto pListViewColumn::setIcon(const image& icon) -> void { + _setState(); +} + +auto pListViewColumn::setResizable(bool resizable) -> void { + _setState(); +} + +auto pListViewColumn::setText(const string& text) -> void { + _setState(); +} + +auto pListViewColumn::setVerticalAlignment(double alignment) -> void { +} + +auto pListViewColumn::setWidth(signed width) -> void { + _setState(); +} + +auto pListViewColumn::_parent() -> maybe { + if(auto parent = self().parentListView()) { + if(auto self = parent->self()) return *self; + } + return nothing; +} + +auto pListViewColumn::_setState() -> void { + if(auto parent = _parent()) { + parent->lock(); + parent->_setIcons(); + utf16_t text(state().text); + LVCOLUMN lvColumn; + lvColumn.mask = LVCF_FMT | LVCF_SUBITEM | LVCF_TEXT | LVCF_WIDTH; + lvColumn.fmt = LVCFMT_CENTER; + lvColumn.iSubItem = self().offset(); + lvColumn.iImage = self().offset(); + lvColumn.pszText = text; + lvColumn.cx = _width; + if(state().horizontalAlignment < 0.333) lvColumn.fmt = LVCFMT_LEFT; + if(state().horizontalAlignment > 0.666) lvColumn.fmt = LVCFMT_RIGHT; + if(state().icon) lvColumn.mask |= LVCF_IMAGE; + if(!state().resizable) lvColumn.fmt |= LVCFMT_FIXED_WIDTH; + ListView_SetColumn(parent->hwnd, self().offset(), &lvColumn); + parent->unlock(); + } +} + +} + +#endif diff --git a/hiro/windows/widget/list-view-column.hpp b/hiro/windows/widget/list-view-column.hpp new file mode 100644 index 00000000..326d7767 --- /dev/null +++ b/hiro/windows/widget/list-view-column.hpp @@ -0,0 +1,28 @@ +#if defined(Hiro_ListView) + +namespace hiro { + +struct pListViewColumn : pObject { + Declare(ListViewColumn, Object) + + auto setActive() -> void; + auto setBackgroundColor(Color color) -> void; + auto setEditable(bool editable) -> void; + auto setExpandable(bool expandable) -> void; + auto setForegroundColor(Color color) -> void; + auto setHorizontalAlignment(double alignment) -> void; + auto setIcon(const image& icon) -> void; + auto setResizable(bool resizable) -> void; + auto setText(const string& text) -> void; + auto setVerticalAlignment(double alignment) -> void; + auto setWidth(signed width) -> void; + + auto _parent() -> maybe; + auto _setState() -> void; + + signed _width = 128; //computed width (via ListView::resizeColumns) +}; + +} + +#endif diff --git a/hiro/windows/widget/list-view-item.cpp b/hiro/windows/widget/list-view-item.cpp new file mode 100644 index 00000000..c4d6371b --- /dev/null +++ b/hiro/windows/widget/list-view-item.cpp @@ -0,0 +1,57 @@ +#if defined(Hiro_ListView) + +namespace hiro { + +auto pListViewItem::construct() -> void { +} + +auto pListViewItem::destruct() -> void { +} + +auto pListViewItem::append(sListViewCell cell) -> void { +} + +auto pListViewItem::remove(sListViewCell cell) -> void { +} + +auto pListViewItem::setBackgroundColor(Color color) -> void { +} + +auto pListViewItem::setChecked(bool checked) -> void { + if(auto parent = _parent()) { + parent->lock(); + ListView_SetCheckState(parent->hwnd, self().offset(), checked); + parent->unlock(); + } +} + +auto pListViewItem::setFocused() -> void { + if(auto parent = _parent()) { + parent->lock(); + ListView_SetItemState(parent->hwnd, self().offset(), LVIS_FOCUSED, LVIS_FOCUSED); + parent->unlock(); + } +} + +auto pListViewItem::setForegroundColor(Color color) -> void { +} + +auto pListViewItem::setSelected(bool selected) -> void { + if(auto parent = _parent()) { + parent->lock(); + unsigned state = selected ? LVIS_SELECTED : 0; + ListView_SetItemState(parent->hwnd, self().offset(), state, LVIS_SELECTED); + parent->unlock(); + } +} + +auto pListViewItem::_parent() -> maybe { + if(auto parent = self().parentListView()) { + if(auto self = parent->self()) return *self; + } + return nothing; +} + +} + +#endif diff --git a/hiro/windows/widget/list-view-item.hpp b/hiro/windows/widget/list-view-item.hpp new file mode 100644 index 00000000..0cbde662 --- /dev/null +++ b/hiro/windows/widget/list-view-item.hpp @@ -0,0 +1,21 @@ +#if defined(Hiro_ListView) + +namespace hiro { + +struct pListViewItem : pObject { + Declare(ListViewItem, Object) + + auto append(sListViewCell cell) -> void; + auto remove(sListViewCell cell) -> void; + auto setBackgroundColor(Color color) -> void; + auto setChecked(bool checked) -> void; + auto setFocused() -> void; + auto setForegroundColor(Color color) -> void; + auto setSelected(bool selected) -> void; + + auto _parent() -> maybe; +}; + +} + +#endif diff --git a/hiro/windows/widget/list-view.cpp b/hiro/windows/widget/list-view.cpp index 7813614b..2f8a6c3a 100644 --- a/hiro/windows/widget/list-view.cpp +++ b/hiro/windows/widget/list-view.cpp @@ -1,288 +1,428 @@ -namespace phoenix { +#if defined(Hiro_ListView) -unsigned ListView_GetColumnCount(HWND hwnd) { - unsigned count = 0; - LVCOLUMN column; - column.mask = LVCF_WIDTH; - while(ListView_GetColumn(hwnd, count++, &column)); - return --count; -} +namespace hiro { -void ListView_SetImage(HWND hwnd, HIMAGELIST imageList, unsigned row, unsigned column, unsigned imageID) { - //if this is the first image assigned, set image list now - //do not set sooner, or image blocks will appear in a list with no images - if(ListView_GetImageList(hwnd, LVSIL_SMALL) != imageList) { - ListView_SetImageList(hwnd, imageList, LVSIL_SMALL); - } - - LVITEM item; - item.mask = LVIF_IMAGE; - item.iItem = row; - item.iSubItem = column; - item.iImage = imageID; - ListView_SetItem(hwnd, &item); -} - -void pListView::append(const lstring& list) { - wchar_t empty[] = L""; - unsigned row = ListView_GetItemCount(hwnd); - LVITEM item; - item.mask = LVIF_TEXT; - item.iItem = row; - item.iSubItem = 0; - item.pszText = empty; - locked = true; - ListView_InsertItem(hwnd, &item); - locked = false; - for(unsigned column = 0; column < list.size(); column++) { - utf16_t wtext(list(column, "")); - ListView_SetItemText(hwnd, row, column, wtext); - } -} - -void pListView::autoSizeColumns() { - unsigned columns = ListView_GetColumnCount(hwnd); - for(unsigned n = 0; n < columns; n++) { - ListView_SetColumnWidth(hwnd, n, LVSCW_AUTOSIZE_USEHEADER); - } -} - -void pListView::remove(unsigned selection) { - ListView_DeleteItem(hwnd, selection); -} - -void pListView::reset() { - ListView_DeleteAllItems(hwnd); - buildImageList(); //free previously allocated images -} - -void pListView::setBackgroundColor(Color color) { - ListView_SetBkColor(hwnd, RGB(color.red, color.green, color.blue)); -} - -void pListView::setCheckable(bool checkable) { - ListView_SetExtendedListViewStyle(hwnd, LVS_EX_FULLROWSELECT | LVS_EX_SUBITEMIMAGES | (checkable ? LVS_EX_CHECKBOXES : 0)); -} - -void pListView::setChecked(unsigned selection, bool checked) { - locked = true; - ListView_SetCheckState(hwnd, selection, checked); - locked = false; -} - -void pListView::setForegroundColor(Color color) { -} - -void pListView::setGeometry(Geometry geometry) { - pWidget::setGeometry(geometry); - autoSizeColumns(); -} - -void pListView::setHeaderText(const lstring& list) { - while(ListView_DeleteColumn(hwnd, 0)); - - lstring headers = list; - if(headers.size() == 0) headers.append(""); //must have at least one column - - for(unsigned n = 0; n < headers.size(); n++) { - LVCOLUMN column; - column.mask = LVCF_FMT | LVCF_TEXT | LVCF_SUBITEM; - column.fmt = LVCFMT_LEFT; - column.iSubItem = n; - utf16_t headerText(headers[n]); - column.pszText = headerText; - ListView_InsertColumn(hwnd, n, &column); - } - autoSizeColumns(); -} - -void pListView::setHeaderVisible(bool visible) { - SetWindowLong( - hwnd, GWL_STYLE, - (GetWindowLong(hwnd, GWL_STYLE) & ~LVS_NOCOLUMNHEADER) | - (visible ? 0 : LVS_NOCOLUMNHEADER) - ); -} - -void pListView::setImage(unsigned selection, unsigned position, const image& image) { - //assign existing image - for(unsigned n = 0; n < images.size(); n++) { - if(images[n] == image) { - imageMap(selection)(position) = n; - return ListView_SetImage(hwnd, imageList, selection, position, n); - } - } - - //append and assign new image - imageMap(selection)(position) = images.size(); - images.append(image); - ImageList_Append(imageList, image, 15); - ListView_SetImage(hwnd, imageList, selection, position, imageMap(selection)(position)); -} - -void pListView::setSelected(bool selected) { - locked = true; - lostFocus = false; - if(selected == false) { - ListView_SetItemState(hwnd, -1, 0, LVIS_FOCUSED | LVIS_SELECTED); - } else { - setSelection(listView.state.selection); - } - locked = false; -} - -void pListView::setSelection(unsigned selection) { - locked = true; - lostFocus = false; - ListView_SetItemState(hwnd, selection, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED); - locked = false; -} - -void pListView::setText(unsigned selection, unsigned position, string text) { - utf16_t wtext(text); - ListView_SetItemText(hwnd, selection, position, wtext); -} - -void pListView::constructor() { - lostFocus = false; +auto pListView::construct() -> void { hwnd = CreateWindowEx( WS_EX_CLIENTEDGE, WC_LISTVIEW, L"", - WS_CHILD | WS_TABSTOP | LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | LVS_NOCOLUMNHEADER, - 0, 0, 0, 0, parentHwnd, (HMENU)id, GetModuleHandle(0), 0 + WS_CHILD | WS_TABSTOP | LVS_REPORT | LVS_SHOWSELALWAYS, + 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0 ); - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&listView); - setDefaultFont(); - setHeaderText(listView.state.headerText); - setBackgroundColor(listView.state.backgroundColor); - setHeaderVisible(listView.state.headerVisible); - setCheckable(listView.state.checkable); - for(auto& text : listView.state.text) append(text); - for(unsigned n = 0; n < listView.state.checked.size(); n++) setChecked(n, listView.state.checked[n]); - buildImageList(); - if(listView.state.selected) setSelection(listView.state.selection); - autoSizeColumns(); - synchronize(); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); + ListView_SetExtendedListViewStyle(hwnd, LVS_EX_FULLROWSELECT | LVS_EX_SUBITEMIMAGES); + pWidget::_setState(); + setBackgroundColor(state().backgroundColor); + setBatchable(state().batchable); + setCheckable(state().checkable); + setGridVisible(state().gridVisible); + setHeaderVisible(state().headerVisible); + setSortable(state().sortable); + _setIcons(); + resizeColumns(); } -void pListView::destructor() { +auto pListView::destruct() -> void { + if(imageList) { ImageList_Destroy(imageList); imageList = 0; } DestroyWindow(hwnd); } -void pListView::orphan() { - destructor(); - constructor(); +auto pListView::append(sListViewColumn column) -> void { + lock(); + wchar_t text[] = L""; + LVCOLUMN lvColumn; + lvColumn.mask = LVCF_FMT | LVCF_TEXT | LVCF_SUBITEM; + lvColumn.fmt = LVCFMT_LEFT; + lvColumn.iSubItem = column->offset(); + lvColumn.pszText = text; + ListView_InsertColumn(hwnd, column->offset(), &lvColumn); + if(auto self = column->self()) { + self->_setState(); + } + resizeColumns(); + unlock(); } -void pListView::buildImageList() { - auto& list = listView.state.image; - unsigned columns = listView.state.text.size(); - unsigned rows = max(1u, listView.state.headerText.size()); - - ListView_SetImageList(hwnd, NULL, LVSIL_SMALL); - if(imageList) ImageList_Destroy(imageList); - imageList = ImageList_Create(15, 15, ILC_COLOR32, 1, 0); - - imageMap.reset(); - images.reset(); - images.append(nall::image()); //empty icon for cells without an image assigned (I_IMAGENONE does not work) - - //create a vector of unique images from all images used (many cells may use the same image) - for(unsigned y = 0; y < list.size(); y++) { - for(unsigned x = 0; x < list[y].size(); x++) { - bool found = false; - for(unsigned z = 0; z < images.size(); z++) { - if(list[y][x] == images[z]) { - found = true; - imageMap(y)(x) = z; - break; - } - } - - if(found == false) { - imageMap(y)(x) = images.size(); - images.append(list[y][x]); - } +auto pListView::append(sListViewItem item) -> void { + lock(); + wchar_t text[] = L""; + LVITEM lvItem; + lvItem.mask = LVIF_TEXT; + lvItem.iItem = item->offset(); + lvItem.iSubItem = 0; + lvItem.pszText = text; + ListView_InsertItem(hwnd, &lvItem); + if(auto self = item->self()) { + self->setChecked(item->state.checked); + self->setSelected(item->state.selected); + } + for(auto& cell : item->state.cells) { + if(auto self = cell->self()) { + self->_setState(); } } + unlock(); +} - //build image list - for(auto& imageItem : images) ImageList_Append(imageList, imageItem, 15); - if(images.size() <= 1) return; - - //set images for all cells - for(unsigned y = 0; y < columns; y++) { - for(unsigned x = 0; x < rows; x++) { - ListView_SetImage(hwnd, imageList, y, x, imageMap(y)(x)); - } +auto pListView::checkAll() -> void { + for(auto& item : state().items) { + item->self()->setChecked(true); } } -void pListView::onActivate(LPARAM lparam) { - LPNMLISTVIEW nmlistview = (LPNMLISTVIEW)lparam; - if(listView.state.text.empty() || !listView.state.selected) return; -//LVN_ITEMACTIVATE is not re-entrant until DispatchMessage() completes -//if(listView.onActivate) listView.onActivate(); - PostMessage(parentHwnd, WM_APP + AppMessage::ListView_onActivate, 0, (LPARAM)&listView); +auto pListView::remove(sListViewColumn column) -> void { + lock(); + ListView_DeleteColumn(hwnd, column->offset()); + unlock(); } -void pListView::onChange(LPARAM lparam) { - LPNMLISTVIEW nmlistview = (LPNMLISTVIEW)lparam; +auto pListView::remove(sListViewItem item) -> void { + lock(); + ListView_DeleteItem(hwnd, item->offset()); + unlock(); +} + +auto pListView::reset() -> void { + lock(); + ListView_DeleteAllItems(hwnd); + LVCOLUMN lvColumn{LVCF_WIDTH}; + while(ListView_GetColumn(hwnd, 0, &lvColumn)) ListView_DeleteColumn(hwnd, 0); + _setIcons(); //free icon resources + unlock(); +} + +auto pListView::resizeColumns() -> void { + lock(); + + vector widths; + signed minimumWidth = 0; + signed expandable = 0; + for(auto column : range(state().columns)) { + signed width = _width(column); + widths.append(width); + minimumWidth += width; + if(state().columns[column]->expandable()) expandable++; + } + + signed maximumWidth = self().geometry().width() - 4; + SCROLLBARINFO sbInfo{sizeof(SCROLLBARINFO)}; + if(GetScrollBarInfo(hwnd, OBJID_VSCROLL, &sbInfo)) { + if(!(sbInfo.rgstate[0] & STATE_SYSTEM_INVISIBLE)) { + maximumWidth -= sbInfo.rcScrollBar.right - sbInfo.rcScrollBar.left; + } + } + + signed expandWidth = 0; + if(expandable && maximumWidth > minimumWidth) { + expandWidth = (maximumWidth - minimumWidth) / expandable; + } + + for(auto column : range(state().columns)) { + if(auto self = state().columns[column]->self()) { + signed width = widths[column]; + if(self->state().expandable) width += expandWidth; + self->_width = width; + self->_setState(); + } + } + + unlock(); +} + +auto pListView::selectAll() -> void { + lock(); + ListView_SetItemState(hwnd, -1, LVIS_SELECTED, LVIS_SELECTED); + unlock(); +} + +auto pListView::setBackgroundColor(Color color) -> void { + if(!color) color = {255, 255, 255}; + ListView_SetBkColor(hwnd, RGB(color.red(), color.green(), color.blue())); +} + +auto pListView::setBatchable(bool batchable) -> void { + auto style = GetWindowLong(hwnd, GWL_STYLE); + !batchable ? style |= LVS_SINGLESEL : style &=~ LVS_SINGLESEL; + SetWindowLong(hwnd, GWL_STYLE, style); +} + +auto pListView::setCheckable(bool checkable) -> void { + auto style = ListView_GetExtendedListViewStyle(hwnd); + checkable ? style |= LVS_EX_CHECKBOXES : style &=~ LVS_EX_CHECKBOXES; + ListView_SetExtendedListViewStyle(hwnd, style); +} + +auto pListView::setForegroundColor(Color color) -> void { +} + +auto pListView::setGridVisible(bool visible) -> void { + //rendered via onCustomDraw +} + +auto pListView::setHeaderVisible(bool visible) -> void { + auto style = GetWindowLong(hwnd, GWL_STYLE); + !visible ? style |= LVS_NOCOLUMNHEADER : style &=~ LVS_NOCOLUMNHEADER; + SetWindowLong(hwnd, GWL_STYLE, style); +} + +auto pListView::setSortable(bool sortable) -> void { + auto style = GetWindowLong(hwnd, GWL_STYLE); + !sortable ? style |= LVS_NOSORTHEADER : style &=~ LVS_NOSORTHEADER; + SetWindowLong(hwnd, GWL_STYLE, style); +} + +auto pListView::uncheckAll() -> void { + for(auto& item : state().items) { + item->self()->setChecked(false); + } +} + +auto pListView::unselectAll() -> void { + lock(); + ListView_SetItemState(hwnd, -1, 0, LVIS_FOCUSED | LVIS_SELECTED); + unlock(); +} + +auto pListView::onActivate(LPARAM lparam) -> void { + auto nmlistview = (LPNMLISTVIEW)lparam; + if(ListView_GetSelectedCount(hwnd) == 0) return; + if(!locked()) { + //LVN_ITEMACTIVATE is not re-entrant until DispatchMessage() completes + //thus, we don't call self().doActivate() here + PostMessageOnce(_parentHandle(), AppMessage::ListView_onActivate, 0, (LPARAM)&reference); + } +} + +auto pListView::onChange(LPARAM lparam) -> void { + auto nmlistview = (LPNMLISTVIEW)lparam; if(!(nmlistview->uChanged & LVIF_STATE)) return; - unsigned selection = nmlistview->iItem; - unsigned imagemask = ((nmlistview->uNewState & LVIS_STATEIMAGEMASK) >> 12) - 1; - if(imagemask == 0 || imagemask == 1) { - if(!locked) { - listView.state.checked[selection] = !listView.state.checked[selection]; - if(listView.onToggle) listView.onToggle(selection); + bool modified = false; + for(auto& item : state().items) { + bool selected = ListView_GetItemState(hwnd, item->offset(), LVIS_SELECTED) & LVIS_SELECTED; + if(item->state.selected != selected) { + modified = true; + item->state.selected = selected; + } + } + if(modified && !locked()) { + //state event change messages are sent for every item + //so when doing a batch select/deselect; this can generate several messages + //we use a delayed AppMessage so that only one callback event is fired off + PostMessageOnce(_parentHandle(), AppMessage::ListView_onChange, 0, (LPARAM)&reference); + } + + unsigned row = nmlistview->iItem; + unsigned mask = ((nmlistview->uNewState & LVIS_STATEIMAGEMASK) >> 12) - 1; + if((mask == 0 || mask == 1) && !locked()) { + if(auto item = self().item(row)) { + item->state.checked = !item->state.checked; + self().doToggle(item); } - } else if((nmlistview->uOldState & LVIS_FOCUSED) && !(nmlistview->uNewState & LVIS_FOCUSED)) { - lostFocus = true; - listView.state.selected = false; - listView.state.selection = 0; - } else if(!(nmlistview->uOldState & LVIS_SELECTED) && (nmlistview->uNewState & LVIS_SELECTED)) { - lostFocus = false; - listView.state.selected = true; - listView.state.selection = selection; - if(!locked && listView.onChange) listView.onChange(); - } else if(!lostFocus && !listView.state.selected) { - lostFocus = false; - listView.state.selected = false; - listView.state.selection = 0; - if(!locked && listView.onChange) listView.onChange(); - } else if(listView.selected() && ListView_GetSelectedCount(hwnd) == 0) { - listView.state.selected = false; - listView.state.selection = 0; - if(!locked && listView.onChange) listView.onChange(); } } -LRESULT pListView::onCustomDraw(LPARAM lparam) { - LPNMLVCUSTOMDRAW lvcd = (LPNMLVCUSTOMDRAW)lparam; +auto pListView::onContext(LPARAM lparam) -> void { + auto nmitemactivate = (LPNMITEMACTIVATE)lparam; + return self().doContext(); +} + +auto pListView::onCustomDraw(LPARAM lparam) -> LRESULT { + auto lvcd = (LPNMLVCUSTOMDRAW)lparam; switch(lvcd->nmcd.dwDrawStage) { - - case CDDS_PREPAINT: { - return CDRF_NOTIFYITEMDRAW; - } - - case CDDS_ITEMPREPAINT: { - Color& background = listView.state.backgroundColor; - Color& foreground = listView.state.foregroundColor; - lvcd->clrText = RGB(foreground.red, foreground.green, foreground.blue); - lvcd->clrTextBk = RGB(background.red, background.green, background.blue); - if(listView.state.headerText.size() >= 2 && lvcd->nmcd.dwItemSpec % 2) { - //draw alternating row colors if there are two or more columns - lvcd->clrTextBk = RGB(max(0, (signed)background.red - 17), max(0, (signed)background.green - 17), max(0, (signed)background.blue - 17)); + default: return CDRF_DODEFAULT; + case CDDS_PREPAINT: return CDRF_NOTIFYITEMDRAW; + case CDDS_ITEMPREPAINT: return CDRF_NOTIFYSUBITEMDRAW | CDRF_NOTIFYPOSTPAINT; + case CDDS_ITEMPREPAINT | CDDS_SUBITEM: return CDRF_SKIPDEFAULT; + case CDDS_ITEMPOSTPAINT: { + HDC hdc = lvcd->nmcd.hdc; + HDC hdcSource = CreateCompatibleDC(hdc); + unsigned row = lvcd->nmcd.dwItemSpec; + for(auto column : range(state().columns)) { + RECT rc, rcLabel; + ListView_GetSubItemRect(hwnd, row, column, LVIR_BOUNDS, &rc); + ListView_GetSubItemRect(hwnd, row, column, LVIR_LABEL, &rcLabel); + rc.right = rcLabel.right; //bounds of column 0 returns width of entire item + signed iconSize = rc.bottom - rc.top - 1; + bool checked = state().items(row)->state.checked; + bool selected = state().items(row)->state.selected; + HBRUSH brush = CreateSolidBrush(selected ? GetSysColor(COLOR_HIGHLIGHT) : CreateRGB(_backgroundColor(row, column))); + FillRect(hdc, &rc, brush); + DeleteObject(brush); + if(state().checkable && column == 0) { + if(auto htheme = OpenThemeData(hwnd, L"BUTTON")) { + unsigned state = checked ? CBS_CHECKEDNORMAL : CBS_UNCHECKEDNORMAL; + SIZE size; + GetThemePartSize(htheme, hdc, BP_CHECKBOX, state, NULL, TS_TRUE, &size); + signed center = max(0, (rc.bottom - rc.top - size.cy) / 2); + RECT rd{rc.left + center, rc.top + center, rc.left + center + size.cx, rc.top + center + size.cy}; + DrawThemeBackground(htheme, hdc, BP_CHECKBOX, state, &rd, NULL); + CloseThemeData(htheme); + } + rc.left += iconSize + 2; + } else { + rc.left += 2; + } + auto cell = self().item(row)->cell(column); + if(!cell) continue; + if(auto icon = cell->state.icon) { + icon.scale(iconSize, iconSize); + icon.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); + auto bitmap = CreateBitmap(icon); + SelectBitmap(hdcSource, bitmap); + BLENDFUNCTION blend{AC_SRC_OVER, 0, (BYTE)(selected ? 128 : 255), AC_SRC_ALPHA}; + AlphaBlend(hdc, rc.left, rc.top, iconSize, iconSize, hdcSource, 0, 0, iconSize, iconSize, blend); + DeleteObject(bitmap); + rc.left += iconSize + 2; + } + if(auto text = cell->state.text) { + auto halign = state().columns(column)->state.horizontalAlignment; + auto valign = state().columns(column)->state.verticalAlignment; + utf16_t wText(text); + SetBkMode(hdc, TRANSPARENT); + SetTextColor(hdc, selected ? GetSysColor(COLOR_HIGHLIGHTTEXT) : CreateRGB(_foregroundColor(row, column))); + auto style = DT_SINGLELINE | DT_END_ELLIPSIS; + style |= halign < 0.333 ? DT_LEFT : halign > 0.666 ? DT_RIGHT : DT_CENTER; + style |= valign < 0.333 ? DT_TOP : valign > 0.666 ? DT_BOTTOM : DT_VCENTER; + rc.right -= 2; + auto font = pFont::create(_font(row, column)); + SelectObject(hdc, font); + DrawText(hdc, wText, -1, &rc, style); + DeleteObject(font); + } + if(state().gridVisible) { + ListView_GetSubItemRect(hwnd, row, column, LVIR_BOUNDS, &rc); + rc.top = rc.bottom - 1; + FillRect(hdc, &rc, (HBRUSH)GetStockObject(LTGRAY_BRUSH)); + ListView_GetSubItemRect(hwnd, row, column, LVIR_LABEL, &rc); + rc.left = rc.right - 1; + FillRect(hdc, &rc, (HBRUSH)GetStockObject(LTGRAY_BRUSH)); + } } - return CDRF_DODEFAULT; + DeleteDC(hdcSource); + return CDRF_SKIPDEFAULT; } - - default: { - return CDRF_DODEFAULT; - } - } } +auto pListView::onSort(LPARAM lparam) -> void { + auto nmlistview = (LPNMLISTVIEW)lparam; + self().doSort(self().column(nmlistview->iSubItem)); } + +auto pListView::_backgroundColor(unsigned _row, unsigned _column) -> Color { + if(auto item = self().item(_row)) { + if(auto cell = item->cell(_column)) { + if(auto color = cell->backgroundColor()) return color; + } + if(auto color = item->backgroundColor()) return color; + } + if(auto column = self().column(_column)) { + if(auto color = column->backgroundColor()) return color; + } + if(auto color = self().backgroundColor()) return color; + if(state().columns.size() >= 2 && _row % 2) return {240, 240, 240}; + return {255, 255, 255}; +} + +auto pListView::_cellWidth(unsigned _row, unsigned _column) -> unsigned { + unsigned width = 6; + if(state().checkable && _column == 0) width += 16 + 2; + if(auto item = self().item(_row)) { + if(auto cell = item->cell(_column)) { + if(auto& icon = cell->state.icon) { + width += 16 + 2; + } + if(auto& text = cell->state.text) { + width += Font::size(_font(_row, _column), text).width(); + } + } + } + return width; +} + +auto pListView::_columnWidth(unsigned _column) -> unsigned { + unsigned width = 12; + if(auto column = self().column(_column)) { + if(auto& icon = column->state.icon) { + width += 16 + 12; //yes; icon spacing in column headers is excessive + } + if(auto& text = column->state.text) { + width += Font::size(self().font(true), text).width(); + } + } + return width; +} + +auto pListView::_font(unsigned _row, unsigned _column) -> string { + if(auto item = self().item(_row)) { + if(auto cell = item->cell(_column)) { + if(auto font = cell->font()) return font; + } + if(auto font = item->font()) return font; + } + if(auto column = self().column(_column)) { + if(auto font = column->font()) return font; + } + if(auto font = self().font(true)) return font; + return Font::sans(8); +} + +auto pListView::_foregroundColor(unsigned _row, unsigned _column) -> Color { + if(auto item = self().item(_row)) { + if(auto cell = item->cell(_column)) { + if(auto color = cell->foregroundColor()) return color; + } + if(auto color = item->foregroundColor()) return color; + } + if(auto column = self().column(_column)) { + if(auto color = column->foregroundColor()) return color; + } + if(auto color = self().foregroundColor()) return color; + return {0, 0, 0}; +} + +auto pListView::_setIcons() -> void { + ListView_SetImageList(hwnd, NULL, LVSIL_SMALL); + if(imageList) ImageList_Destroy(imageList); + imageList = ImageList_Create(16, 16, ILC_COLOR32, 1, 0); + ListView_SetImageList(hwnd, imageList, LVSIL_SMALL); + + for(auto column : range(state().columns)) { + auto icon = state().columns(column)->state.icon; + if(icon) { + icon.scale(16, 16); + icon.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); + } else { + icon.allocate(16, 16); + icon.fill(0x00ffffff); + } + auto bitmap = CreateBitmap(icon); + ImageList_Add(imageList, bitmap, NULL); + DeleteObject(bitmap); + } + + //empty icon used for ListViewItems (drawn manually via onCustomDraw) + image icon; + icon.allocate(16, 16); + icon.fill(0x00ffffff); + auto bitmap = CreateBitmap(icon); + ImageList_Add(imageList, bitmap, NULL); + DeleteObject(bitmap); +} + +auto pListView::_width(unsigned column) -> unsigned { + if(auto width = state().columns[column]->width()) return width; + unsigned width = 1; + if(state().headerVisible) { + width = max(width, _columnWidth(column)); + } + for(auto row : range(state().items)) { + width = max(width, _cellWidth(row, column)); + } + return width; +} + +} + +#endif diff --git a/hiro/windows/widget/list-view.hpp b/hiro/windows/widget/list-view.hpp new file mode 100644 index 00000000..32b171e7 --- /dev/null +++ b/hiro/windows/widget/list-view.hpp @@ -0,0 +1,47 @@ +#if defined(Hiro_ListView) + +namespace hiro { + +struct pListView : pWidget { + Declare(ListView, Widget) + + auto append(sListViewColumn column) -> void; + auto append(sListViewItem item) -> void; + auto checkAll() -> void; + auto remove(sListViewColumn column) -> void; + auto remove(sListViewItem item) -> void; + auto reset() -> void; + auto resizeColumns() -> void; + auto selectAll() -> void; + auto setBackgroundColor(Color color) -> void; + auto setBatchable(bool batchable) -> void; + auto setCheckable(bool checkable) -> void; + auto setChecked(bool checked) -> void; + auto setForegroundColor(Color color) -> void; + auto setGridVisible(bool visible) -> void; + auto setHeaderVisible(bool visible) -> void; + auto setSortable(bool sortable) -> void; + auto uncheckAll() -> void; + auto unselectAll() -> void; + + auto onActivate(LPARAM lparam) -> void; + auto onChange(LPARAM lparam) -> void; + auto onContext(LPARAM lparam) -> void; + auto onCustomDraw(LPARAM lparam) -> LRESULT; + auto onSort(LPARAM lparam) -> void; + + auto _backgroundColor(unsigned row, unsigned column) -> Color; + auto _cellWidth(unsigned row, unsigned column) -> unsigned; + auto _columnWidth(unsigned column) -> unsigned; + auto _font(unsigned row, unsigned column) -> string; + auto _foregroundColor(unsigned row, unsigned column) -> Color; + auto _setIcons() -> void; + auto _width(unsigned column) -> unsigned; + + HIMAGELIST imageList = 0; + vector icons; +}; + +} + +#endif diff --git a/hiro/windows/widget/progress-bar.cpp b/hiro/windows/widget/progress-bar.cpp index ebd24bc6..deff42b7 100644 --- a/hiro/windows/widget/progress-bar.cpp +++ b/hiro/windows/widget/progress-bar.cpp @@ -1,31 +1,30 @@ -namespace phoenix { +#if defined(Hiro_ProgressBar) -Size pProgressBar::minimumSize() { - return {0, 23}; -} +namespace hiro { -void pProgressBar::setPosition(unsigned position) { - SendMessage(hwnd, PBM_SETPOS, (WPARAM)position, 0); -} - -void pProgressBar::constructor() { +auto pProgressBar::construct() -> void { hwnd = CreateWindow(PROGRESS_CLASS, L"", WS_CHILD | PBS_SMOOTH, - 0, 0, 0, 0, parentHwnd, (HMENU)id, GetModuleHandle(0), 0); - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&progressBar); + 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); + pWidget::_setState(); SendMessage(hwnd, PBM_SETRANGE, 0, MAKELPARAM(0, 100)); SendMessage(hwnd, PBM_SETSTEP, MAKEWPARAM(1, 0), 0); - setPosition(progressBar.state.position); - synchronize(); + setPosition(state().position); } -void pProgressBar::destructor() { +auto pProgressBar::destruct() -> void { DestroyWindow(hwnd); } -void pProgressBar::orphan() { - destructor(); - constructor(); +auto pProgressBar::minimumSize() const -> Size { + return {0, 23}; +} + +auto pProgressBar::setPosition(unsigned position) -> void { + SendMessage(hwnd, PBM_SETPOS, (WPARAM)position, 0); } } + +#endif diff --git a/hiro/windows/widget/progress-bar.hpp b/hiro/windows/widget/progress-bar.hpp new file mode 100644 index 00000000..fc462a93 --- /dev/null +++ b/hiro/windows/widget/progress-bar.hpp @@ -0,0 +1,14 @@ +#if defined(Hiro_ProgressBar) + +namespace hiro { + +struct pProgressBar : pWidget { + Declare(ProgressBar, Widget) + + auto minimumSize() const -> Size override; + auto setPosition(unsigned position) -> void; +}; + +} + +#endif diff --git a/hiro/windows/widget/radio-button.cpp b/hiro/windows/widget/radio-button.cpp index d7545cd4..284eb559 100644 --- a/hiro/windows/widget/radio-button.cpp +++ b/hiro/windows/widget/radio-button.cpp @@ -1,89 +1,107 @@ -namespace phoenix { +#if defined(Hiro_RadioButton) -Size pRadioButton::minimumSize() { - Size size = pFont::size(hfont, radioButton.state.text); +namespace hiro { - if(radioButton.state.orientation == Orientation::Horizontal) { - size.width += radioButton.state.image.width; - size.height = max(radioButton.state.image.height, size.height); - } - - if(radioButton.state.orientation == Orientation::Vertical) { - size.width = max(radioButton.state.image.width, size.width); - size.height += radioButton.state.image.height; - } - - return {size.width + 20, size.height + 10}; -} - -void pRadioButton::setChecked() { - for(auto& item : radioButton.state.group) { - SendMessage(item.p.hwnd, BM_SETCHECK, (WPARAM)(&item == &radioButton), 0); - } -} - -void pRadioButton::setGroup(const group& group) { -} - -void pRadioButton::setImage(const image& image, Orientation orientation) { - nall::image nallImage = image; - nallImage.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); - - if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } - if(himagelist) { ImageList_Destroy(himagelist); himagelist = 0; } - - if(OsVersion() < WindowsVista) nallImage.alphaBlend(GetSysColor(COLOR_BTNFACE)); - hbitmap = CreateBitmap(nallImage); - himagelist = ImageList_Create(nallImage.width, nallImage.height, ILC_COLOR32, 1, 0); - ImageList_Add(himagelist, hbitmap, NULL); - BUTTON_IMAGELIST list; - list.himl = himagelist; - switch(orientation) { - case Orientation::Horizontal: SetRect(&list.margin, 5, 0, 0, 0); list.uAlign = BUTTON_IMAGELIST_ALIGN_LEFT; break; - case Orientation::Vertical: SetRect(&list.margin, 0, 5, 0, 0); list.uAlign = BUTTON_IMAGELIST_ALIGN_TOP; break; - } - Button_SetImageList(hwnd, &list); - - setText(radioButton.state.text); -} - -void pRadioButton::setText(string text) { - if(text.empty()) { - SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) | BS_BITMAP); - } else { - SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) & ~BS_BITMAP); - } - - SetWindowText(hwnd, utf16_t(text)); -} - -void pRadioButton::constructor() { +auto pRadioButton::construct() -> void { hwnd = CreateWindow(L"BUTTON", L"", WS_CHILD | WS_TABSTOP | BS_CHECKBOX | BS_PUSHLIKE, - 0, 0, 0, 0, parentHwnd, (HMENU)id, GetModuleHandle(0), 0); - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&radioButton); - setDefaultFont(); - if(radioButton.state.checked) setChecked(); - setImage(radioButton.state.image, radioButton.state.orientation); -//setText(radioButton.state.text); - synchronize(); + 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); + pWidget::_setState(); + _setState(); + setBordered(state().bordered); + if(state().checked) setChecked(); } -void pRadioButton::destructor() { +auto pRadioButton::destruct() -> void { if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } if(himagelist) { ImageList_Destroy(himagelist); himagelist = 0; } DestroyWindow(hwnd); } -void pRadioButton::orphan() { - destructor(); - constructor(); +auto pRadioButton::minimumSize() -> Size { + auto size = pFont::size(hfont, state().text); + + if(state().orientation == Orientation::Horizontal) { + size.setWidth(size.width() + state().icon.width); + size.setHeight(max(size.height(), state().icon.height)); + } + + if(state().orientation == Orientation::Vertical) { + size.setWidth(max(size.width(), state().icon.width)); + size.setHeight(size.height() + state().icon.height); + } + + return {size.width() + (state().text ? 20 : 10), size.height() + 10}; +} + +auto pRadioButton::setBordered(bool bordered) -> void { +} + +auto pRadioButton::setChecked() -> void { + if(auto group = self().group()) { + for(auto& weak : group->state.objects) { + if(auto object = weak.acquire()) { + if(auto radioButton = dynamic_cast(object.data())) { + if(auto self = radioButton->self()) { + SendMessage(self->hwnd, BM_SETCHECK, (WPARAM)(&self->reference == &reference), 0); + } + } + } + } + } +} + +auto pRadioButton::setGroup(sGroup group) -> void { +} + +auto pRadioButton::setIcon(const image& icon) -> void { + _setState(); +} + +auto pRadioButton::setOrientation(Orientation orientation) -> void { + _setState(); +} + +void pRadioButton::setText(const string& text) { + _setState(); } void pRadioButton::onActivate() { - if(radioButton.state.checked) return; - radioButton.setChecked(); - if(radioButton.onActivate) radioButton.onActivate(); + if(state().checked) return; + self().setChecked(); + self().doActivate(); +} + +auto pRadioButton::_setState() -> void { + image icon = state().icon; + icon.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); + + if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } + if(himagelist) { ImageList_Destroy(himagelist); himagelist = 0; } + + if(OsVersion() < WindowsVista) icon.alphaBlend(GetSysColor(COLOR_BTNFACE)); + + hbitmap = CreateBitmap(icon); + himagelist = ImageList_Create(icon.width, icon.height, ILC_COLOR32, 1, 0); + ImageList_Add(himagelist, hbitmap, NULL); + BUTTON_IMAGELIST list; + list.himl = himagelist; + switch(state().orientation) { + case Orientation::Horizontal: SetRect(&list.margin, 5, 0, 0, 0); list.uAlign = BUTTON_IMAGELIST_ALIGN_LEFT; break; + case Orientation::Vertical: SetRect(&list.margin, 0, 5, 0, 0); list.uAlign = BUTTON_IMAGELIST_ALIGN_TOP; break; + } + Button_SetImageList(hwnd, &list); + + if(auto text = state().text) { + SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) &~ BS_BITMAP); + SetWindowText(hwnd, utf16_t(text)); + } else { + SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) | BS_BITMAP); + SetWindowText(hwnd, L""); + } } } + +#endif diff --git a/hiro/windows/widget/radio-button.hpp b/hiro/windows/widget/radio-button.hpp new file mode 100644 index 00000000..dbf71a11 --- /dev/null +++ b/hiro/windows/widget/radio-button.hpp @@ -0,0 +1,26 @@ +#if defined(Hiro_RadioButton) + +namespace hiro { + +struct pRadioButton : pWidget { + Declare(RadioButton, Widget) + + auto minimumSize() -> Size; + auto setBordered(bool bordered) -> void; + auto setChecked() -> void; + auto setGroup(sGroup group) -> void override; + auto setIcon(const image& icon) -> void; + auto setOrientation(Orientation orientation) -> void; + auto setText(const string& text) -> void; + + auto onActivate() -> void; + + auto _setState() -> void; + + HBITMAP hbitmap = 0; + HIMAGELIST himagelist = 0; +}; + +} + +#endif diff --git a/hiro/windows/widget/radio-label.cpp b/hiro/windows/widget/radio-label.cpp index 925f1cda..01231102 100644 --- a/hiro/windows/widget/radio-label.cpp +++ b/hiro/windows/widget/radio-label.cpp @@ -1,49 +1,55 @@ -namespace phoenix { +#if defined(Hiro_RadioLabel) -Size pRadioLabel::minimumSize() { - Size size = pFont::size(hfont, radioLabel.state.text); - return {size.width + 20, size.height + 4}; -} +namespace hiro { -void pRadioLabel::setChecked() { - for(auto& item : radioLabel.state.group) { - SendMessage(item.p.hwnd, BM_SETCHECK, (WPARAM)(&item == &radioLabel), 0); - } -} - -void pRadioLabel::setGroup(const group& group) { -} - -void pRadioLabel::setText(string text) { - SetWindowText(hwnd, utf16_t(text)); -} - -void pRadioLabel::constructor() { +auto pRadioLabel::construct() -> void { hwnd = CreateWindow( L"BUTTON", L"", WS_CHILD | WS_TABSTOP | BS_RADIOBUTTON, - 0, 0, 0, 0, parentHwnd, (HMENU)id, GetModuleHandle(0), 0 + 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0 ); - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&radioLabel); - setDefaultFont(); - if(radioLabel.state.checked) setChecked(); - setText(radioLabel.state.text); - synchronize(); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); + pWidget::_setState(); + if(state().checked) setChecked(); + setText(state().text); } -void pRadioLabel::destructor() { +auto pRadioLabel::destruct() -> void { DestroyWindow(hwnd); } -void pRadioLabel::orphan() { - destructor(); - constructor(); +auto pRadioLabel::minimumSize() -> Size { + auto size = pFont::size(hfont, state().text); + return {size.width() + 20, size.height() + 4}; } -void pRadioLabel::onActivate() { - if(radioLabel.state.checked) return; - radioLabel.setChecked(); - if(radioLabel.onActivate) radioLabel.onActivate(); +auto pRadioLabel::setChecked() -> void { + if(auto group = self().group()) { + for(auto& weak : group->state.objects) { + if(auto object = weak.acquire()) { + if(auto radioLabel = dynamic_cast(object.data())) { + if(auto self = radioLabel->self()) { + SendMessage(self->hwnd, BM_SETCHECK, (WPARAM)(&self->reference == &reference), 0); + } + } + } + } + } +} + +auto pRadioLabel::setGroup(sGroup group) -> void { +} + +auto pRadioLabel::setText(const string& text) -> void { + SetWindowText(hwnd, utf16_t(text)); +} + +auto pRadioLabel::onActivate() -> void { + if(state().checked) return; + self().setChecked(); + self().doActivate(); } } + +#endif diff --git a/hiro/windows/widget/radio-label.hpp b/hiro/windows/widget/radio-label.hpp new file mode 100644 index 00000000..38b5b6f7 --- /dev/null +++ b/hiro/windows/widget/radio-label.hpp @@ -0,0 +1,18 @@ +#if defined(Hiro_RadioLabel) + +namespace hiro { + +struct pRadioLabel : pWidget { + Declare(RadioLabel, Widget) + + auto minimumSize() -> Size; + auto setChecked() -> void; + auto setGroup(sGroup group) -> void override; + auto setText(const string& text) -> void; + + auto onActivate() -> void; +}; + +} + +#endif diff --git a/hiro/windows/widget/tab-frame-item.cpp b/hiro/windows/widget/tab-frame-item.cpp new file mode 100644 index 00000000..2583d648 --- /dev/null +++ b/hiro/windows/widget/tab-frame-item.cpp @@ -0,0 +1,63 @@ +#if defined(Hiro_TabFrame) + +namespace hiro { + +auto pTabFrameItem::construct() -> void { +} + +auto pTabFrameItem::destruct() -> void { +} + +auto pTabFrameItem::append(sLayout layout) -> void { + if(auto parent = _parent()) { + parent->_synchronizeLayout(); + } +} + +auto pTabFrameItem::remove(sLayout layout) -> void { + if(auto parent = _parent()) { + parent->_synchronizeLayout(); + } +} + +auto pTabFrameItem::setClosable(bool closable) -> void { + //unsupported +} + +auto pTabFrameItem::setIcon(const image& icon) -> void { + if(auto parent = _parent()) { + parent->_buildImageList(); + } +} + +auto pTabFrameItem::setMovable(bool movable) -> void { + //unsupported +} + +auto pTabFrameItem::setSelected() -> void { + if(auto parent = _parent()) { + TabCtrl_SetCurSel(parent->hwnd, self().offset()); + parent->_synchronizeLayout(); + } +} + +auto pTabFrameItem::setText(const string& text) -> void { + if(auto parent = _parent()) { + utf16_t wText(text); + TCITEM tcItem; + tcItem.mask = TCIF_TEXT; + tcItem.pszText = (wchar_t*)wText; + TabCtrl_SetItem(parent->hwnd, self().offset(), &tcItem); + } +} + +auto pTabFrameItem::_parent() -> maybe { + if(auto parent = self().parentTabFrame()) { + if(auto self = parent->self()) return *self; + } + return nothing; +} + +} + +#endif diff --git a/hiro/windows/widget/tab-frame-item.hpp b/hiro/windows/widget/tab-frame-item.hpp new file mode 100644 index 00000000..d08a2696 --- /dev/null +++ b/hiro/windows/widget/tab-frame-item.hpp @@ -0,0 +1,21 @@ +#if defined(Hiro_TabFrame) + +namespace hiro { + +struct pTabFrameItem : pObject { + Declare(TabFrameItem, Object) + + auto append(sLayout layout) -> void; + auto remove(sLayout layout) -> void; + auto setClosable(bool closable) -> void; + auto setIcon(const image& icon) -> void; + auto setMovable(bool movable) -> void; + auto setSelected() -> void; + auto setText(const string& text) -> void; + + auto _parent() -> maybe; +}; + +} + +#endif diff --git a/hiro/windows/widget/tab-frame.cpp b/hiro/windows/widget/tab-frame.cpp index f447586e..5c9e3700 100644 --- a/hiro/windows/widget/tab-frame.cpp +++ b/hiro/windows/widget/tab-frame.cpp @@ -1,128 +1,127 @@ -namespace phoenix { +#if defined(Hiro_TabFrame) -static LRESULT CALLBACK TabFrame_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { - Object* object = (Object*)GetWindowLongPtr(hwnd, GWLP_USERDATA); - if(object == nullptr) return DefWindowProc(hwnd, msg, wparam, lparam); - TabFrame& tabFrame = (TabFrame&)*object; - return Shared_windowProc(tabFrame.p.windowProc, hwnd, msg, wparam, lparam); -} +namespace hiro { -void pTabFrame::append(string text, const image& image) { - unsigned selection = TabCtrl_GetItemCount(hwnd); - wchar_t wtext[] = L""; - TCITEM item; - item.mask = TCIF_TEXT; - item.pszText = wtext; - TabCtrl_InsertItem(hwnd, selection, &item); - setText(selection, text); - if(!image.empty()) setImage(selection, image); -} - -void pTabFrame::remove(unsigned selection) { - TabCtrl_DeleteItem(hwnd, selection); - buildImageList(); -} - -void pTabFrame::setEnabled(bool enabled) { - pWidget::setEnabled(enabled); - for(auto& layout : tabFrame.state.layout) { - if(layout) layout->setEnabled(layout->enabled()); +static auto CALLBACK TabFrame_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT { + if(auto object = (mObject*)GetWindowLongPtr(hwnd, GWLP_USERDATA)) { + if(auto tabFrame = dynamic_cast(object)) { + return Shared_windowProc(tabFrame->self()->windowProc, hwnd, msg, wparam, lparam); + } } + return DefWindowProc(hwnd, msg, wparam, lparam); } -void pTabFrame::setGeometry(Geometry geometry) { - pWidget::setGeometry(geometry); - geometry.x += 1, geometry.width -= 4; - geometry.y += 21, geometry.height -= 23; - for(auto& layout : tabFrame.state.layout) { - if(layout) layout->setGeometry(geometry); - } -} - -void pTabFrame::setImage(unsigned selection, const image& image) { - buildImageList(); -} - -void pTabFrame::setSelection(unsigned selection) { - TabCtrl_SetCurSel(hwnd, selection); - synchronizeLayout(); -} - -void pTabFrame::setText(unsigned selection, string text) { - utf16_t wtext(text); - TCITEM item; - item.mask = TCIF_TEXT; - item.pszText = (wchar_t*)wtext; - TabCtrl_SetItem(hwnd, selection, &item); -} - -void pTabFrame::setVisible(bool visible) { - pWidget::setVisible(visible); - for(auto& layout : tabFrame.state.layout) { - if(layout) layout->setVisible(layout->visible()); - } -} - -void pTabFrame::constructor() { +auto pTabFrame::construct() -> void { hwnd = CreateWindow(WC_TABCONTROL, L"", WS_CHILD | WS_TABSTOP, - 0, 0, 0, 0, parentHwnd, (HMENU)id, GetModuleHandle(0), 0); - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&tabFrame); - + 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); windowProc = (WindowProc)GetWindowLongPtr(hwnd, GWLP_WNDPROC); SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)TabFrame_windowProc); - - setDefaultFont(); - for(auto& text : tabFrame.state.text) append(text, {}); - buildImageList(); - setSelection(tabFrame.state.selection); - synchronize(); + pWidget::_setState(); + for(auto& item : state().items) append(item); } -void pTabFrame::destructor() { +auto pTabFrame::destruct() -> void { if(imageList) { ImageList_Destroy(imageList); imageList = nullptr; } DestroyWindow(hwnd); } -void pTabFrame::orphan() { - destructor(); - constructor(); +auto pTabFrame::append(sTabFrameItem item) -> void { + wchar_t text[] = L""; + TCITEM tcItem; + tcItem.mask = TCIF_TEXT; + tcItem.pszText = text; + TabCtrl_InsertItem(hwnd, item->offset(), &tcItem); + if(auto self = item->self()) { + self->setClosable(item->state.closable); + self->setIcon(item->state.icon); + self->setMovable(item->state.movable); + self->setText(item->state.text); + if(item->selected()) self->setSelected(); + } + _buildImageList(); + _synchronizeLayout(); } -void pTabFrame::buildImageList() { - if(imageList) ImageList_Destroy(imageList); - unsigned size = pFont::size(hfont, " ").height; +auto pTabFrame::remove(sTabFrameItem item) -> void { + TabCtrl_DeleteItem(hwnd, item->offset()); + _buildImageList(); +} + +auto pTabFrame::setEdge(Edge edge) -> void { + //unsupported +} + +auto pTabFrame::setEnabled(bool enabled) -> void { + pWidget::setEnabled(enabled); + for(auto& item : state().items) { + if(auto layout = item->state.layout) { + if(auto self = layout->self()) self->setEnabled(layout->enabled(true)); + } + } +} + +auto pTabFrame::setGeometry(Geometry geometry) -> void { + pWidget::setGeometry(geometry); + geometry.setX(geometry.x() + 1); + geometry.setY(geometry.y() + 21); + geometry.setWidth(geometry.width() - 4); + geometry.setHeight(geometry.height() - 23); + for(auto& item : state().items) { + if(auto layout = item->state.layout) { + layout->setGeometry(geometry); + } + } +} + +auto pTabFrame::setVisible(bool visible) -> void { + pWidget::setVisible(visible); + for(auto& item : state().items) { + if(auto layout = item->state.layout) { + if(auto self = layout->self()) self->setVisible(layout->visible(true)); + } + } +} + +auto pTabFrame::_buildImageList() -> void { + unsigned size = pFont::size(hfont, " ").height(); + + if(imageList) { ImageList_Destroy(imageList); imageList = nullptr; } imageList = ImageList_Create(size, size, ILC_COLOR32, 1, 0); - for(auto& image : tabFrame.state.image) { - ImageList_Append(imageList, image, size); + for(auto& item : state().items) { + ImageList_Append(imageList, item->state.icon, size); } TabCtrl_SetImageList(hwnd, imageList); - for(unsigned n = 0; n < tabFrame.state.image.size(); n++) { - TCITEM item; - item.mask = TCIF_IMAGE; - item.iImage = (tabFrame.state.image(n).empty() ? -1 : n); - TabCtrl_SetItem(hwnd, n, &item); + for(auto offset : range(state().items)) { + TCITEM tcItem; + tcItem.mask = TCIF_IMAGE; + tcItem.iImage = state().items[offset]->state.icon ? offset : -1; + TabCtrl_SetItem(hwnd, offset, &tcItem); } } -void pTabFrame::synchronizeLayout() { - unsigned selection = 0; - for(auto& layout : tabFrame.state.layout) { - if(layout) layout->setVisible(selection == tabFrame.state.selection); - selection++; +auto pTabFrame::_synchronizeLayout() -> void { + for(auto& item : state().items) { + if(auto layout = item->state.layout) { + layout->setVisible(item->selected()); + } } } -void pTabFrame::onChange() { - tabFrame.state.selection = TabCtrl_GetCurSel(hwnd); - synchronizeLayout(); - if(tabFrame.onChange) tabFrame.onChange(); +auto pTabFrame::onChange() -> void { + unsigned selected = TabCtrl_GetCurSel(hwnd); + for(auto& item : state().items) item->state.selected = false; + if(auto item = self().item(selected)) item->state.selected = true; + _synchronizeLayout(); + self().doChange(); } //called only if TCS_OWNERDRAWFIXED style is used //this style disables XP/Vista theming of the TabFrame -void pTabFrame::onDrawItem(LPARAM lparam) { - LPDRAWITEMSTRUCT item = (LPDRAWITEMSTRUCT)lparam; +auto pTabFrame::onDrawItem(LPARAM lparam) -> void { +/* + auto item = (LPDRAWITEMSTRUCT)lparam; FillRect(item->hDC, &item->rcItem, GetSysColorBrush(COLOR_3DFACE)); SetBkMode(item->hDC, TRANSPARENT); SetTextColor(item->hDC, GetSysColor(COLOR_BTNTEXT)); @@ -138,6 +137,9 @@ void pTabFrame::onDrawItem(LPARAM lparam) { } TextOut(item->hDC, item->rcItem.left + (width - size.width) / 2, item->rcItem.top + 2, utf16_t(text), text.size()); } +*/ } } + +#endif diff --git a/hiro/windows/widget/tab-frame.hpp b/hiro/windows/widget/tab-frame.hpp new file mode 100644 index 00000000..cfbd0d8a --- /dev/null +++ b/hiro/windows/widget/tab-frame.hpp @@ -0,0 +1,27 @@ +#if defined(Hiro_TabFrame) + +namespace hiro { + +struct pTabFrame : pWidget { + Declare(TabFrame, Widget) + + auto append(sTabFrameItem item) -> void; + auto remove(sTabFrameItem item) -> void; + auto setEdge(Edge edge) -> void; + auto setEnabled(bool enabled) -> void override; + auto setGeometry(Geometry geometry) -> void override; + auto setVisible(bool visible) -> void override; + + auto onChange() -> void; + auto onDrawItem(LPARAM lparam) -> void; + + auto _buildImageList() -> void; + auto _synchronizeLayout() -> void; + + WindowProc windowProc = nullptr; + HIMAGELIST imageList = nullptr; +}; + +} + +#endif diff --git a/hiro/windows/widget/text-edit.cpp b/hiro/windows/widget/text-edit.cpp index 7ce02d7a..81255b06 100644 --- a/hiro/windows/widget/text-edit.cpp +++ b/hiro/windows/widget/text-edit.cpp @@ -1,39 +1,60 @@ -namespace phoenix { +#if defined(Hiro_TextEdit) -void pTextEdit::setBackgroundColor(Color color) { - if(backgroundBrush) DeleteObject(backgroundBrush); - backgroundBrush = CreateSolidBrush(RGB(color.red, color.green, color.blue)); +namespace hiro { + +auto pTextEdit::construct() -> void { + hwnd = CreateWindowEx( + WS_EX_CLIENTEDGE, L"EDIT", L"", + WS_CHILD | WS_TABSTOP | WS_VSCROLL | ES_AUTOVSCROLL | ES_MULTILINE | ES_WANTRETURN | (!state().wordWrap ? WS_HSCROLL | ES_AUTOHSCROLL : 0), + 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); + pWidget::_setState(); + setBackgroundColor(state().backgroundColor); + setCursorPosition(state().cursorPosition); + setEditable(state().editable); + setText(state().text); } -void pTextEdit::setCursorPosition(unsigned position) { +auto pTextEdit::destruct() -> void { + state().text = text(); + if(backgroundBrush) { DeleteObject(backgroundBrush); backgroundBrush = 0; } + DestroyWindow(hwnd); +} + +auto pTextEdit::setBackgroundColor(Color color) -> void { + if(backgroundBrush) { DeleteObject(backgroundBrush); backgroundBrush = 0; } + backgroundBrush = CreateSolidBrush(color ? CreateRGB(color) : GetSysColor(COLOR_WINDOW)); +} + +auto pTextEdit::setCursorPosition(unsigned position) -> void { if(position == ~0) position >>= 1; //Edit_SetSel takes signed type Edit_SetSel(hwnd, position, position); Edit_ScrollCaret(hwnd); } -void pTextEdit::setEditable(bool editable) { +auto pTextEdit::setEditable(bool editable) -> void { SendMessage(hwnd, EM_SETREADONLY, editable == false, (LPARAM)0); } -void pTextEdit::setForegroundColor(Color color) { +auto pTextEdit::setForegroundColor(Color color) -> void { } -void pTextEdit::setText(string text) { - locked = true; - string output = text; - output.replace("\r", ""); - output.replace("\n", "\r\n"); - SetWindowText(hwnd, utf16_t(output)); - locked = false; +auto pTextEdit::setText(string text) -> void { + lock(); + text.replace("\r", ""); + text.replace("\n", "\r\n"); + SetWindowText(hwnd, utf16_t(text)); + unlock(); } -void pTextEdit::setWordWrap(bool wordWrap) { +auto pTextEdit::setWordWrap(bool wordWrap) -> void { //ES_AUTOHSCROLL cannot be changed after widget creation. //As a result, we must destroy and re-create widget to change this setting. - orphan(); + reconstruct(); } -string pTextEdit::text() { +auto pTextEdit::text() const -> string { unsigned length = GetWindowTextLength(hwnd); wchar_t buffer[length + 1]; GetWindowText(hwnd, buffer, length + 1); @@ -43,34 +64,10 @@ string pTextEdit::text() { return text; } -void pTextEdit::constructor() { - hwnd = CreateWindowEx( - WS_EX_CLIENTEDGE, L"EDIT", L"", - WS_CHILD | WS_TABSTOP | WS_VSCROLL | ES_AUTOVSCROLL | ES_MULTILINE | ES_WANTRETURN | (textEdit.state.wordWrap == false ? WS_HSCROLL | ES_AUTOHSCROLL : 0), - 0, 0, 0, 0, parentHwnd, (HMENU)id, GetModuleHandle(0), 0 - ); - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&textEdit); - setDefaultFont(); - setBackgroundColor(textEdit.state.backgroundColor); - setCursorPosition(textEdit.state.cursorPosition); - setEditable(textEdit.state.editable); - setText(textEdit.state.text); - synchronize(); -} - -void pTextEdit::destructor() { - textEdit.state.text = text(); - DestroyWindow(hwnd); -} - -void pTextEdit::orphan() { - destructor(); - constructor(); -} - -void pTextEdit::onChange() { - if(locked) return; - if(textEdit.onChange) textEdit.onChange(); +auto pTextEdit::onChange() -> void { + if(!locked()) self().doChange(); } } + +#endif diff --git a/hiro/windows/widget/text-edit.hpp b/hiro/windows/widget/text-edit.hpp new file mode 100644 index 00000000..061d12af --- /dev/null +++ b/hiro/windows/widget/text-edit.hpp @@ -0,0 +1,23 @@ +#if defined(Hiro_TextEdit) + +namespace hiro { + +struct pTextEdit : pWidget { + Declare(TextEdit, Widget) + + auto setBackgroundColor(Color color) -> void; + auto setCursorPosition(unsigned position) -> void; + auto setEditable(bool editable) -> void; + auto setForegroundColor(Color color) -> void; + auto setText(string text) -> void; + auto setWordWrap(bool wordWrap) -> void; + auto text() const -> string; + + auto onChange() -> void; + + HBRUSH backgroundBrush = nullptr; +}; + +} + +#endif diff --git a/hiro/windows/widget/vertical-scroller.cpp b/hiro/windows/widget/vertical-scroller.cpp index 009779ca..f1d00e50 100644 --- a/hiro/windows/widget/vertical-scroller.cpp +++ b/hiro/windows/widget/vertical-scroller.cpp @@ -1,45 +1,42 @@ -namespace phoenix { +#if defined(Hiro_VerticalScroller) -Size pVerticalScroller::minimumSize() { - return {18, 0}; -} +namespace hiro { -void pVerticalScroller::setLength(unsigned length) { - length += (length == 0); - SetScrollRange(hwnd, SB_CTL, 0, length - 1, TRUE); - verticalScroller.setPosition(0); -} - -void pVerticalScroller::setPosition(unsigned position) { - SetScrollPos(hwnd, SB_CTL, position, TRUE); -} - -void pVerticalScroller::constructor() { +auto pVerticalScroller::construct() -> void { hwnd = CreateWindow( L"SCROLLBAR", L"", WS_CHILD | SBS_VERT, - 0, 0, 0, 0, parentHwnd, (HMENU)id, GetModuleHandle(0), 0 + 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0 ); - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&verticalScroller); - unsigned position = verticalScroller.state.position; - setLength(verticalScroller.state.length); - verticalScroller.setPosition(position); - synchronize(); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); + pWidget::_setState(); + setLength(state().length); + setPosition(state().position); } -void pVerticalScroller::destructor() { +auto pVerticalScroller::destruct() -> void { DestroyWindow(hwnd); } -void pVerticalScroller::orphan() { - destructor(); - constructor(); +auto pVerticalScroller::minimumSize() const -> Size { + return {18, 0}; } -void pVerticalScroller::onChange(WPARAM wparam) { +auto pVerticalScroller::setLength(unsigned length) -> void { + length += (length == 0); + SetScrollRange(hwnd, SB_CTL, 0, length - 1, TRUE); +} + +auto pVerticalScroller::setPosition(unsigned position) -> void { + SetScrollPos(hwnd, SB_CTL, position, TRUE); +} + +auto pVerticalScroller::onChange(WPARAM wparam) -> void { unsigned position = ScrollEvent(hwnd, wparam); - if(position == verticalScroller.state.position) return; - verticalScroller.state.position = position; - if(verticalScroller.onChange) verticalScroller.onChange(); + if(position == state().position) return; + state().position = position; + self().doChange(); } } + +#endif diff --git a/hiro/windows/widget/vertical-scroller.hpp b/hiro/windows/widget/vertical-scroller.hpp new file mode 100644 index 00000000..acafd1fd --- /dev/null +++ b/hiro/windows/widget/vertical-scroller.hpp @@ -0,0 +1,17 @@ +#if defined(Hiro_VerticalScroller) + +namespace hiro { + +struct pVerticalScroller : pWidget { + Declare(VerticalScroller, Widget) + + auto minimumSize() const -> Size override; + auto setLength(unsigned length) -> void; + auto setPosition(unsigned position) -> void; + + auto onChange(WPARAM wparam) -> void; +}; + +} + +#endif diff --git a/hiro/windows/widget/vertical-slider.cpp b/hiro/windows/widget/vertical-slider.cpp index 006526c7..57435d71 100644 --- a/hiro/windows/widget/vertical-slider.cpp +++ b/hiro/windows/widget/vertical-slider.cpp @@ -1,47 +1,43 @@ -namespace phoenix { +#if defined(Hiro_VerticalSlider) -Size pVerticalSlider::minimumSize() { - return {0, 25}; -} +namespace hiro { -void pVerticalSlider::setLength(unsigned length) { - length += (length == 0); - SendMessage(hwnd, TBM_SETRANGE, (WPARAM)true, (LPARAM)MAKELONG(0, length - 1)); - SendMessage(hwnd, TBM_SETPAGESIZE, 0, (LPARAM)(length >> 3)); - verticalSlider.setPosition(0); -} - -void pVerticalSlider::setPosition(unsigned position) { - SendMessage(hwnd, TBM_SETPOS, (WPARAM)true, (LPARAM)position); -} - -void pVerticalSlider::constructor() { +auto pVerticalSlider::construct() -> void { hwnd = CreateWindow( TRACKBAR_CLASS, L"", WS_CHILD | WS_TABSTOP | TBS_TRANSPARENTBKGND | TBS_NOTICKS | TBS_BOTH | TBS_VERT, - 0, 0, 0, 0, parentHwnd, (HMENU)id, GetModuleHandle(0), 0 + 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0 ); - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&verticalSlider); - - unsigned position = verticalSlider.state.position; - setLength(verticalSlider.state.length); - verticalSlider.setPosition(position); - synchronize(); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); + pWidget::_setState(); + setLength(state().length); + setPosition(state().position); } -void pVerticalSlider::destructor() { +auto pVerticalSlider::destruct() -> void { DestroyWindow(hwnd); } -void pVerticalSlider::orphan() { - destructor(); - constructor(); +auto pVerticalSlider::minimumSize() const -> Size { + return {0, 25}; } -void pVerticalSlider::onChange() { +auto pVerticalSlider::setLength(unsigned length) -> void { + length += (length == 0); + SendMessage(hwnd, TBM_SETRANGE, (WPARAM)true, (LPARAM)MAKELONG(0, length - 1)); + SendMessage(hwnd, TBM_SETPAGESIZE, 0, (LPARAM)(length >> 3)); +} + +auto pVerticalSlider::setPosition(unsigned position) -> void { + SendMessage(hwnd, TBM_SETPOS, (WPARAM)true, (LPARAM)position); +} + +auto pVerticalSlider::onChange() -> void { unsigned position = SendMessage(hwnd, TBM_GETPOS, 0, 0); - if(position == verticalSlider.state.position) return; - verticalSlider.state.position = position; - if(verticalSlider.onChange) verticalSlider.onChange(); + if(position == state().position) return; + state().position = position; + self().doChange(); } } + +#endif diff --git a/hiro/windows/widget/vertical-slider.hpp b/hiro/windows/widget/vertical-slider.hpp new file mode 100644 index 00000000..bfb4b9f0 --- /dev/null +++ b/hiro/windows/widget/vertical-slider.hpp @@ -0,0 +1,17 @@ +#if defined(Hiro_VerticalSlider) + +namespace hiro { + +struct pVerticalSlider : pWidget { + Declare(VerticalSlider, Widget) + + auto minimumSize() const -> Size; + auto setLength(unsigned length) -> void; + auto setPosition(unsigned position) -> void; + + auto onChange() -> void; +}; + +} + +#endif diff --git a/hiro/windows/widget/viewport.cpp b/hiro/windows/widget/viewport.cpp index e3400965..d93b076b 100644 --- a/hiro/windows/widget/viewport.cpp +++ b/hiro/windows/widget/viewport.cpp @@ -1,17 +1,16 @@ -namespace phoenix { +#if defined(Hiro_Viewport) -static LRESULT CALLBACK Viewport_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { - Object* object = (Object*)GetWindowLongPtr(hwnd, GWLP_USERDATA); - if(object == nullptr) return DefWindowProc(hwnd, msg, wparam, lparam); - if(!dynamic_cast(object)) return DefWindowProc(hwnd, msg, wparam, lparam); - Viewport& viewport = (Viewport&)*object; +namespace hiro { + +static auto CALLBACK Viewport_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT { + auto object = (mObject*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if(!object) return DefWindowProc(hwnd, msg, wparam, lparam); + auto viewport = dynamic_cast(object); + if(!viewport) return DefWindowProc(hwnd, msg, wparam, lparam); if(msg == WM_DROPFILES) { - lstring paths = DropPaths(wparam); - if(paths.empty() == false) { - if(viewport.onDrop) viewport.onDrop(paths); - } - return FALSE; + if(auto paths = DropPaths(wparam)) viewport->doDrop(paths); + return false; } if(msg == WM_GETDLGCODE) { @@ -19,58 +18,55 @@ static LRESULT CALLBACK Viewport_windowProc(HWND hwnd, UINT msg, WPARAM wparam, } if(msg == WM_MOUSEMOVE) { - TRACKMOUSEEVENT tracker = {sizeof(TRACKMOUSEEVENT), TME_LEAVE, hwnd}; + TRACKMOUSEEVENT tracker{sizeof(TRACKMOUSEEVENT), TME_LEAVE, hwnd}; TrackMouseEvent(&tracker); - if(viewport.onMouseMove) viewport.onMouseMove({(int16_t)LOWORD(lparam), (int16_t)HIWORD(lparam)}); + viewport->doMouseMove({(int16_t)LOWORD(lparam), (int16_t)HIWORD(lparam)}); } if(msg == WM_MOUSELEAVE) { - if(viewport.onMouseLeave) viewport.onMouseLeave(); + viewport->doMouseLeave(); } if(msg == WM_LBUTTONDOWN || msg == WM_MBUTTONDOWN || msg == WM_RBUTTONDOWN) { - if(viewport.onMousePress) switch(msg) { - case WM_LBUTTONDOWN: viewport.onMousePress(Mouse::Button::Left); break; - case WM_MBUTTONDOWN: viewport.onMousePress(Mouse::Button::Middle); break; - case WM_RBUTTONDOWN: viewport.onMousePress(Mouse::Button::Right); break; + switch(msg) { + case WM_LBUTTONDOWN: viewport->doMousePress(Mouse::Button::Left); break; + case WM_MBUTTONDOWN: viewport->doMousePress(Mouse::Button::Middle); break; + case WM_RBUTTONDOWN: viewport->doMousePress(Mouse::Button::Right); break; } } if(msg == WM_LBUTTONUP || msg == WM_MBUTTONUP || msg == WM_RBUTTONUP) { - if(viewport.onMouseRelease) switch(msg) { - case WM_LBUTTONUP: viewport.onMouseRelease(Mouse::Button::Left); break; - case WM_MBUTTONUP: viewport.onMouseRelease(Mouse::Button::Middle); break; - case WM_RBUTTONUP: viewport.onMouseRelease(Mouse::Button::Right); break; + switch(msg) { + case WM_LBUTTONUP: viewport->doMouseRelease(Mouse::Button::Left); break; + case WM_MBUTTONUP: viewport->doMouseRelease(Mouse::Button::Middle); break; + case WM_RBUTTONUP: viewport->doMouseRelease(Mouse::Button::Right); break; } } return DefWindowProc(hwnd, msg, wparam, lparam); } -uintptr_t pViewport::handle() { - return (uintptr_t)hwnd; -} - -void pViewport::setDroppable(bool droppable) { - DragAcceptFiles(hwnd, droppable); -} - -void pViewport::constructor() { - hwnd = CreateWindow(L"phoenix_viewport", L"", +auto pViewport::construct() -> void { + hwnd = CreateWindow(L"hiroViewport", L"", WS_CHILD | WS_DISABLED, - 0, 0, 0, 0, parentHwnd, (HMENU)id, GetModuleHandle(0), 0); - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&viewport); - setDroppable(viewport.state.droppable); - synchronize(); + 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); + pWidget::_setState(); + setDroppable(state().droppable); } -void pViewport::destructor() { +auto pViewport::destruct() -> void { DestroyWindow(hwnd); } -void pViewport::orphan() { - destructor(); - constructor(); +auto pViewport::handle() const -> uintptr_t { + return (uintptr_t)hwnd; +} + +auto pViewport::setDroppable(bool droppable) -> void { + DragAcceptFiles(hwnd, droppable); } } + +#endif diff --git a/hiro/windows/widget/viewport.hpp b/hiro/windows/widget/viewport.hpp new file mode 100644 index 00000000..ed0b17f7 --- /dev/null +++ b/hiro/windows/widget/viewport.hpp @@ -0,0 +1,14 @@ +#if defined(Hiro_Viewport) + +namespace hiro { + +struct pViewport : pWidget { + Declare(Viewport, Widget) + + auto handle() const -> uintptr_t; + auto setDroppable(bool droppable) -> void; +}; + +} + +#endif diff --git a/hiro/windows/widget/widget.cpp b/hiro/windows/widget/widget.cpp index 2f09b00d..7b79f8ff 100644 --- a/hiro/windows/widget/widget.cpp +++ b/hiro/windows/widget/widget.cpp @@ -1,82 +1,90 @@ -namespace phoenix { +#if defined(Hiro_Widget) -bool pWidget::focused() { +namespace hiro { + +auto pWidget::construct() -> void { + abstract = true; + //todo: create hiroWidget + hwnd = CreateWindow(L"hiroLabel", L"", WS_CHILD, 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); + _setState(); +} + +auto pWidget::destruct() -> void { + DeleteObject(hfont); + DestroyWindow(hwnd); +} + +auto pWidget::focused() -> bool { return GetFocus() == hwnd; } -Size pWidget::minimumSize() { +auto pWidget::minimumSize() -> Size { return {0, 0}; } -void pWidget::setEnabled(bool enabled) { - if(!widget.parent()) enabled = false; - if(widget.state.abstract) enabled = false; - if(!widget.enabledToAll()) enabled = false; +auto pWidget::setEnabled(bool enabled) -> void { + if(!self().parentWindow(true)) enabled = false; + if(!self().enabled(true)) enabled = false; + if(abstract) enabled = false; EnableWindow(hwnd, enabled); } -void pWidget::setFocused() { +auto pWidget::setFocused() -> void { SetFocus(hwnd); } -void pWidget::setFont(string font) { +auto pWidget::setFont(const string&) -> void { + auto font = self().font(true); + if(!font) font = Font::sans(8); if(hfont) DeleteObject(hfont); hfont = pFont::create(font); SendMessage(hwnd, WM_SETFONT, (WPARAM)hfont, 0); } -void pWidget::setGeometry(Geometry geometry) { - if(GetParentWidget(&sizable)) { - Position displacement = GetParentWidget(&sizable)->state.geometry.position(); - geometry.x -= displacement.x; - geometry.y -= displacement.y; +auto pWidget::setGeometry(Geometry geometry) -> void { + if(auto parent = _parentWidget()) { + Position displacement = parent->geometry().position(); + geometry.setX(geometry.x() - displacement.x()); + geometry.setY(geometry.y() - displacement.y()); } - SetWindowPos(hwnd, NULL, geometry.x, geometry.y, geometry.width, geometry.height, SWP_NOZORDER); - if(widget.onSize) widget.onSize(); + SetWindowPos(hwnd, NULL, geometry.x(), geometry.y(), geometry.width(), geometry.height(), SWP_NOZORDER); + self().doSize(); } -void pWidget::setVisible(bool visible) { - if(!widget.parent()) visible = false; - if(widget.state.abstract) visible = false; - if(!widget.visibleToAll()) visible = false; +auto pWidget::setVisible(bool visible) -> void { + if(!self().parentWindow(true)) visible = false; + if(!self().visible(true)) visible = false; + if(abstract) visible = false; ShowWindow(hwnd, visible ? SW_SHOWNORMAL : SW_HIDE); } -void pWidget::constructor() { - hfont = pFont::create(Font::sans(8)); - if(widget.state.abstract) { - hwnd = CreateWindow(L"phoenix_label", L"", - WS_CHILD, - 0, 0, 0, 0, parentHwnd, (HMENU)id, GetModuleHandle(0), 0); - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&widget); - } +// + +auto pWidget::_parentHandle() -> HWND { + if(auto parent = _parentWidget()) return parent->self()->hwnd; + if(auto parent = _parentWindow()) return parent->self()->hwnd; + return 0; } -void pWidget::destructor() { - if(widget.state.abstract) { - DestroyWindow(hwnd); - } +auto pWidget::_parentWidget() -> maybe { + #if defined(Hiro_TabFrame) + if(auto parent = self().parentTabFrame(true)) return *parent; + #endif + return nothing; } -void pWidget::orphan() { - destructor(); - constructor(); +auto pWidget::_parentWindow() -> maybe { + if(auto parent = self().parentWindow(true)) return *parent; + return nothing; } -void pWidget::setDefaultFont() { - string description = widget.state.font; - if(description.empty()) description = Font::sans(8); - hfont = pFont::create(description); - SendMessage(hwnd, WM_SETFONT, (WPARAM)hfont, 0); -} - -//calling Widget::setParent destroys widget and re-creates it: -//need to re-apply visiblity and enabled status; called by each subclassed setParent() function -//constructors are called top-down, so set each widget to the top of the z-order (so children appear on top of parents) -void pWidget::synchronize() { - widget.setEnabled(widget.enabled()); - widget.setVisible(widget.visible()); - SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); +auto pWidget::_setState() -> void { + setEnabled(self().enabled()); + setFont(self().font()); + setVisible(self().visible()); } } + +#endif diff --git a/hiro/windows/widget/widget.hpp b/hiro/windows/widget/widget.hpp new file mode 100644 index 00000000..c613f554 --- /dev/null +++ b/hiro/windows/widget/widget.hpp @@ -0,0 +1,28 @@ +#if defined(Hiro_Widget) + +namespace hiro { + +struct pWidget : pSizable { + Declare(Widget, Sizable) + + auto focused() -> bool; + virtual auto minimumSize() -> Size; + auto setEnabled(bool enabled) -> void override; + auto setFocused() -> void; + auto setFont(const string& font) -> void override; + virtual auto setGeometry(Geometry geometry) -> void; + auto setVisible(bool visible) -> void override; + + auto _parentHandle() -> HWND; + auto _parentWidget() -> maybe; + auto _parentWindow() -> maybe; + auto _setState() -> void; + + bool abstract = false; + HWND hwnd = 0; + HFONT hfont = 0; +}; + +} + +#endif diff --git a/hiro/windows/window.cpp b/hiro/windows/window.cpp index 79b64c4e..c05230fa 100644 --- a/hiro/windows/window.cpp +++ b/hiro/windows/window.cpp @@ -1,94 +1,210 @@ -namespace phoenix { +#if defined(Hiro_Window) + +namespace hiro { vector pWindow::modal; -//EnableWindow(hwnd, false) sends WM_KILLFOCUS; deactivating said window -//EnableWindow(hwnd, true) does not restore lost focus -//when a modal loop finishes, and the dialog is dismissed, the application loses focus entirely -//due to anti-focus-stealing code in Windows, SetForegroundWindow() cannot restore lost focus -//further, GetActiveWindow() returns nothing when all windows have lost focus -//thus, we must use a focus-stealing hack to reclaim the focus we never intended to dismiss; -//and we must replicate GetActiveWindow() by scanning the Z-order of windows for this process - -void pWindow::updateModality() { - //bind thread input to process that currently has input focus - auto threadId = GetWindowThreadProcessId(GetForegroundWindow(), NULL); - AttachThreadInput(threadId, GetCurrentThreadId(), TRUE); - - pWindow* topMost = nullptr; - for(auto& object : pObject::objects) { - if(dynamic_cast(object) == nullptr) continue; - pWindow* p = (pWindow*)object; - bool enable = modal.size() == 0 || modal.find(p); - if(IsWindowEnabled(p->hwnd) != enable) EnableWindow(p->hwnd, enable); - if(enable && p->window.visible()) { - if(topMost == nullptr) topMost = p; - else if(GetWindowZOrder(p->hwnd) < GetWindowZOrder(topMost->hwnd)) topMost = p; - } - } - - //set input focus on top-most window - if(topMost) { - SetForegroundWindow(topMost->hwnd); - SetActiveWindow(topMost->hwnd); - } - - //unbind thread input hook - AttachThreadInput(threadId, GetCurrentThreadId(), FALSE); -} - static const unsigned FixedStyle = WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX | WS_BORDER; static const unsigned ResizableStyle = WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_THICKFRAME; -Window& pWindow::none() { - static Window* window = nullptr; - if(window == nullptr) window = new Window; - return *window; +auto pWindow::construct() -> void { + hwnd = CreateWindow(L"hiroWindow", L"", ResizableStyle, 128, 128, 256, 256, 0, 0, GetModuleHandle(0), 0); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); + setDroppable(state().droppable); + setGeometry({128, 128, 256, 256}); } -void pWindow::append(Layout& layout) { - Geometry geom = window.state.geometry; - geom.x = geom.y = 0; - layout.setGeometry(geom); +auto pWindow::destruct() -> void { + if(hbrush) { DeleteObject(hbrush); hbrush = nullptr; } + DestroyWindow(hwnd); } -void pWindow::append(Menu& menu) { - menu.p.parentWindow = &window; - updateMenu(); +auto pWindow::append(sLayout layout) -> void { } -void pWindow::append(Widget& widget) { - if(GetParentWidget(&widget)) { - widget.p.parentHwnd = GetParentWidget(&widget)->p.hwnd; - } else { - widget.p.parentHwnd = window.p.hwnd; - } - widget.p.orphan(); - - if(widget.font().empty() && !window.state.widgetFont.empty()) { - widget.setFont(window.state.widgetFont); - } +auto pWindow::append(sMenuBar menuBar) -> void { } -bool pWindow::focused() { +auto pWindow::append(sStatusBar statusBar) -> void { +} + +auto pWindow::focused() const -> bool override { return (GetForegroundWindow() == hwnd); } -Geometry pWindow::frameMargin() { - unsigned style = window.state.resizable ? ResizableStyle : FixedStyle; - if(window.state.fullScreen) style = 0; - RECT rc = {0, 0, 640, 480}; - AdjustWindowRect(&rc, style, window.state.menuVisible); - unsigned statusHeight = 0; - if(window.state.statusVisible) { - RECT src; - GetClientRect(hstatus, &src); - statusHeight = src.bottom - src.top; +auto pWindow::frameMargin() const -> Geometry { + unsigned style = state().resizable ? ResizableStyle : FixedStyle; + if(state().fullScreen) style = 0; + RECT rc{0, 0, 640, 480}; + AdjustWindowRect(&rc, style, (bool)GetMenu(hwnd)); + signed statusHeight = 0; + if(auto statusBar = state().statusBar) { + if(auto self = statusBar->self()) { + if(statusBar->visible()) { + RECT src; + GetClientRect(self->hwnd, &src); + statusHeight = src.bottom - src.top; + } + } } return {abs(rc.left), abs(rc.top), (rc.right - rc.left) - 640, (rc.bottom - rc.top) + statusHeight - 480}; } -Geometry pWindow::geometry() { +auto pWindow::remove(sLayout layout) -> void { +} + +auto pWindow::remove(sMenuBar menuBar) -> void { +} + +auto pWindow::remove(sStatusBar statusBar) -> void { +} + +auto pWindow::setBackgroundColor(Color color) -> void { + hbrushColor = CreateRGB(color); + if(hbrush) { DeleteObject(hbrush); hbrush = nullptr; } + if(color) hbrush = CreateSolidBrush(hbrushColor); +} + +auto pWindow::setDroppable(bool droppable) -> void { + DragAcceptFiles(hwnd, droppable); +} + +auto pWindow::setEnabled(bool enabled) -> void { +} + +auto pWindow::setFocused() -> void { + if(!self().visible()) self().setVisible(true); + SetFocus(hwnd); +} + +auto pWindow::setFullScreen(bool fullScreen) -> void { + lock(); + if(fullScreen == false) { + SetWindowLongPtr(hwnd, GWL_STYLE, WS_VISIBLE | (state().resizable ? ResizableStyle : FixedStyle)); + setGeometry(state().geometry); + } else { + HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + MONITORINFOEX info; + memset(&info, 0, sizeof(MONITORINFOEX)); + info.cbSize = sizeof(MONITORINFOEX); + GetMonitorInfo(monitor, &info); + RECT rc = info.rcMonitor; + Geometry geometry = {rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top}; + SetWindowLongPtr(hwnd, GWL_STYLE, WS_VISIBLE | WS_POPUP); + Geometry margin = frameMargin(); + setGeometry({ + geometry.x() + margin.x(), geometry.y() + margin.y(), + geometry.width() - margin.width(), geometry.height() - margin.height() + }); + } + unlock(); +} + +auto pWindow::setGeometry(Geometry geometry) -> void { + lock(); + Geometry margin = frameMargin(); + SetWindowPos( + hwnd, NULL, + geometry.x() - margin.x(), geometry.y() - margin.y(), + geometry.width() + margin.width(), geometry.height() + margin.height(), + SWP_NOZORDER | SWP_FRAMECHANGED + ); + if(auto statusBar = state().statusBar) { + if(auto self = statusBar->self()) { + SetWindowPos(self->hwnd, nullptr, 0, 0, 0, 0, SWP_NOZORDER | SWP_FRAMECHANGED); + } + } + if(auto layout = state().layout) { + layout->setGeometry(geometry.setPosition(0, 0)); + } + unlock(); +} + +auto pWindow::setModal(bool modality) -> void { + if(modality) { + modal.appendOnce(this); + while(state().modal) { + Application::processEvents(); + if(Application::state.onMain) { + Application::doMain(); + } else { + usleep(20 * 1000); + } + } + if(auto position = modal.find(this)) modal.remove(position()); + } +} + +auto pWindow::setResizable(bool resizable) -> void { + SetWindowLongPtr(hwnd, GWL_STYLE, state().resizable ? ResizableStyle : FixedStyle); + setGeometry(state().geometry); +} + +auto pWindow::setTitle(string text) -> void { + SetWindowText(hwnd, utf16_t(text)); +} + +auto pWindow::setVisible(bool visible) -> void { + ShowWindow(hwnd, visible ? SW_SHOWNORMAL : SW_HIDE); + if(!visible) setModal(false); +} + +// + +auto pWindow::onClose() -> void { + if(state().onClose) self().doClose(); + else self().setVisible(false); + if(state().modal && !self().visible()) self().setModal(false); +} + +auto pWindow::onDrop(WPARAM wparam) -> void { + lstring paths = DropPaths(wparam); + if(paths.empty()) return; + self().doDrop(paths); +} + +auto pWindow::onEraseBackground() -> bool { + if(hbrush == 0) return false; + RECT rc; + GetClientRect(hwnd, &rc); + PAINTSTRUCT ps; + BeginPaint(hwnd, &ps); + FillRect(ps.hdc, &rc, hbrush); + EndPaint(hwnd, &ps); + return true; +} + +auto pWindow::onModalBegin() -> void { + Application::Windows::doModalChange(true); +} + +auto pWindow::onModalEnd() -> void { + Application::Windows::doModalChange(false); +} + +auto pWindow::onMove() -> void { + if(locked()) return; + state().geometry.setPosition(_geometry().position()); + self().doMove(); +} + +auto pWindow::onSize() -> void { + if(locked()) return; + if(auto statusBar = state().statusBar) { + if(auto self = statusBar->self()) { + SetWindowPos(self->hwnd, nullptr, 0, 0, 0, 0, SWP_NOZORDER | SWP_FRAMECHANGED); + } + } + state().geometry.setSize(_geometry().size()); + if(auto layout = state().layout) { + layout->setGeometry(_geometry().setPosition(0, 0)); + } + self().doSize(); +} + +// + +auto pWindow::_geometry() -> Geometry { Geometry margin = frameMargin(); RECT rc; @@ -101,239 +217,14 @@ Geometry pWindow::geometry() { GetWindowRect(hwnd, &rc); } - signed x = rc.left + margin.x; - signed y = rc.top + margin.y; - unsigned width = (rc.right - rc.left) - margin.width; - unsigned height = (rc.bottom - rc.top) - margin.height; + signed x = rc.left + margin.x(); + signed y = rc.top + margin.y(); + signed width = (rc.right - rc.left) - margin.width(); + signed height = (rc.bottom - rc.top) - margin.height(); return {x, y, width, height}; } -void pWindow::remove(Layout& layout) { } -void pWindow::remove(Menu& menu) { - updateMenu(); -} - -void pWindow::remove(Widget& widget) { - widget.p.orphan(); -} - -void pWindow::setBackgroundColor(Color color) { - if(brush) DeleteObject(brush); - brushColor = RGB(color.red, color.green, color.blue); - brush = CreateSolidBrush(brushColor); -} - -void pWindow::setDroppable(bool droppable) { - DragAcceptFiles(hwnd, droppable); -} - -void pWindow::setFocused() { - if(window.state.visible == false) setVisible(true); - SetFocus(hwnd); -} - -void pWindow::setFullScreen(bool fullScreen) { - locked = true; - if(fullScreen == false) { - SetWindowLongPtr(hwnd, GWL_STYLE, WS_VISIBLE | (window.state.resizable ? ResizableStyle : FixedStyle)); - setGeometry(window.state.geometry); - } else { - HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); - MONITORINFOEX info; - memset(&info, 0, sizeof(MONITORINFOEX)); - info.cbSize = sizeof(MONITORINFOEX); - GetMonitorInfo(monitor, &info); - RECT rc = info.rcMonitor; - Geometry geometry = {rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top}; - SetWindowLongPtr(hwnd, GWL_STYLE, WS_VISIBLE | WS_POPUP); - Geometry margin = frameMargin(); - setGeometry({ - geometry.x + margin.x, geometry.y + margin.y, - geometry.width - margin.width, geometry.height - margin.height - }); - } - locked = false; -} - -void pWindow::setGeometry(Geometry geometry) { - locked = true; - Geometry margin = frameMargin(); - SetWindowPos( - hwnd, NULL, - geometry.x - margin.x, geometry.y - margin.y, - geometry.width + margin.width, geometry.height + margin.height, - SWP_NOZORDER | SWP_FRAMECHANGED - ); - SetWindowPos(hstatus, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_FRAMECHANGED); - for(auto& layout : window.state.layout) { - Geometry geom = this->geometry(); - geom.x = geom.y = 0; - layout.setGeometry(geom); - } - locked = false; -} - -void pWindow::setMenuFont(string font) { -} - -void pWindow::setMenuVisible(bool visible) { - locked = true; - SetMenu(hwnd, visible ? hmenu : 0); - setGeometry(window.state.geometry); - locked = false; -} - -void pWindow::setModal(bool modality) { - if(modality == true) { - modal.appendOnce(this); - updateModality(); - while(window.state.modal) { - Application::processEvents(); - if(Application::main) { - Application::main(); - } else { - usleep(20 * 1000); - } - } - if(auto position = modal.find(this)) modal.remove(position()); - updateModality(); - } -} - -void pWindow::setResizable(bool resizable) { - SetWindowLongPtr(hwnd, GWL_STYLE, window.state.resizable ? ResizableStyle : FixedStyle); - setGeometry(window.state.geometry); -} - -void pWindow::setStatusFont(string font) { - if(hstatusfont) DeleteObject(hstatusfont); - hstatusfont = pFont::create(font); - SendMessage(hstatus, WM_SETFONT, (WPARAM)hstatusfont, 0); -} - -void pWindow::setStatusText(string text) { - SendMessage(hstatus, SB_SETTEXT, 0, (LPARAM)(wchar_t*)utf16_t(text)); -} - -void pWindow::setStatusVisible(bool visible) { - locked = true; - ShowWindow(hstatus, visible ? SW_SHOWNORMAL : SW_HIDE); - setGeometry(window.state.geometry); - locked = false; -} - -void pWindow::setTitle(string text) { - SetWindowText(hwnd, utf16_t(text)); -} - -void pWindow::setVisible(bool visible) { - ShowWindow(hwnd, visible ? SW_SHOWNORMAL : SW_HIDE); - if(visible == false) setModal(false); -} - -void pWindow::setWidgetFont(string font) { -} - -void pWindow::constructor() { - brush = 0; - - hwnd = CreateWindow(L"phoenix_window", L"", ResizableStyle, 128, 128, 256, 256, 0, 0, GetModuleHandle(0), 0); - hmenu = CreateMenu(); - hstatus = CreateWindow(STATUSCLASSNAME, L"", WS_CHILD, 0, 0, 0, 0, hwnd, 0, GetModuleHandle(0), 0); - hstatusfont = 0; - setStatusFont(Font::sans(8)); - - //status bar will be capable of receiving tab focus if it is not disabled - SetWindowLongPtr(hstatus, GWL_STYLE, GetWindowLong(hstatus, GWL_STYLE) | WS_DISABLED); - - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&window); - setDroppable(window.state.droppable); - setGeometry({128, 128, 256, 256}); - - DWORD color = GetSysColor(COLOR_3DFACE); - window.state.backgroundColor = Color((uint8_t)(color >> 16), (uint8_t)(color >> 8), (uint8_t)(color >> 0), 255u); -} - -void pWindow::destructor() { - DeleteObject(hstatusfont); - DestroyWindow(hstatus); - DestroyMenu(hmenu); - DestroyWindow(hwnd); -} - -void pWindow::updateMenu() { - if(hmenu) DestroyMenu(hmenu); - hmenu = CreateMenu(); - - for(auto& menu : window.state.menu) { - menu.p.update(window); - if(menu.visible()) { - AppendMenu(hmenu, MF_STRING | MF_POPUP, (UINT_PTR)menu.p.hmenu, utf16_t(menu.state.text)); - } - } - - SetMenu(hwnd, window.state.menuVisible ? hmenu : 0); -} - -void pWindow::onClose() { - if(window.onClose) window.onClose(); - else window.setVisible(false); - if(window.state.modal && !window.state.visible) window.setModal(false); -} - -void pWindow::onDrop(WPARAM wparam) { - lstring paths = DropPaths(wparam); - if(paths.empty()) return; - if(window.onDrop) window.onDrop(paths); -} - -bool pWindow::onEraseBackground() { - if(brush == 0) return false; - RECT rc; - GetClientRect(hwnd, &rc); - PAINTSTRUCT ps; - BeginPaint(hwnd, &ps); - FillRect(ps.hdc, &rc, brush); - EndPaint(hwnd, &ps); - return true; -} - -void pWindow::onModalBegin() { - if(Application::Windows::onModalBegin) Application::Windows::onModalBegin(); -} - -void pWindow::onModalEnd() { - if(Application::Windows::onModalEnd) Application::Windows::onModalEnd(); -} - -void pWindow::onMove() { - if(locked) return; - - Geometry windowGeometry = geometry(); - window.state.geometry.x = windowGeometry.x; - window.state.geometry.y = windowGeometry.y; - - if(window.onMove) window.onMove(); -} - -void pWindow::onSize() { - if(locked) return; - SetWindowPos(hstatus, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_FRAMECHANGED); - - Geometry windowGeometry = geometry(); - window.state.geometry.width = windowGeometry.width; - window.state.geometry.height = windowGeometry.height; - - for(auto& layout : window.state.layout) { - Geometry geom = geometry(); - geom.x = geom.y = 0; - layout.setGeometry(geom); - } - - if(window.onSize) window.onSize(); -} - -} +#endif diff --git a/hiro/windows/window.hpp b/hiro/windows/window.hpp new file mode 100644 index 00000000..e22448b9 --- /dev/null +++ b/hiro/windows/window.hpp @@ -0,0 +1,47 @@ +#if defined(Hiro_Window) + +namespace hiro { + +struct pWindow : pObject { + Declare(Window, Object) + + auto append(sLayout layout) -> void; + auto append(sMenuBar menuBar) -> void; + auto append(sStatusBar statusBar) -> void; + auto focused() const -> bool override; + auto frameMargin() const -> Geometry; + auto remove(sLayout layout) -> void; + auto remove(sMenuBar menuBar) -> void; + auto remove(sStatusBar statusBar) -> void; + auto setBackgroundColor(Color color) -> void; + auto setDroppable(bool droppable) -> void; + auto setEnabled(bool enabled) -> void; + auto setFocused() -> void; + auto setFullScreen(bool fullScreen) -> void; + auto setGeometry(Geometry geometry) -> void; + auto setModal(bool modal) -> void; + auto setResizable(bool resizable) -> void; + auto setTitle(string text) -> void; + auto setVisible(bool visible) -> void; + + auto onClose() -> void; + auto onDrop(WPARAM wparam) -> void; + auto onEraseBackground() -> bool; + auto onModalBegin() -> void; + auto onModalEnd() -> void; + auto onMove() -> void; + auto onSize() -> void; + + auto _geometry() -> Geometry; + + HWND hwnd = nullptr; + HFONT hstatusfont = nullptr; + HBRUSH hbrush = nullptr; + COLORREF hbrushColor = 0; + + static vector modal; +}; + +} + +#endif diff --git a/nall/image/base.hpp b/nall/image/base.hpp index a530671a..f05ede90 100644 --- a/nall/image/base.hpp +++ b/nall/image/base.hpp @@ -19,11 +19,11 @@ struct image { unsigned depth; unsigned shift; - inline bool operator==(const channel& source) { + inline bool operator==(const channel& source) const { return mask == source.mask && depth == source.depth && shift == source.shift; } - inline bool operator!=(const channel& source) { + inline bool operator!=(const channel& source) const { return !operator==(source); } }; @@ -48,8 +48,8 @@ struct image { //core.hpp inline explicit operator bool() const; - inline bool operator==(const image& source); - inline bool operator!=(const image& source); + inline bool operator==(const image& source) const; + inline bool operator!=(const image& source) const; inline image& operator=(const image& source); inline image& operator=(image&& source); diff --git a/nall/image/core.hpp b/nall/image/core.hpp index c0f0a63c..3d2da38a 100644 --- a/nall/image/core.hpp +++ b/nall/image/core.hpp @@ -7,7 +7,7 @@ image::operator bool() const { return !empty(); } -bool image::operator==(const image& source) { +bool image::operator==(const image& source) const { if(width != source.width) return false; if(height != source.height) return false; if(pitch != source.pitch) return false; @@ -23,11 +23,12 @@ bool image::operator==(const image& source) { return memcmp(data, source.data, width * height * stride) == 0; } -bool image::operator!=(const image& source) { +bool image::operator!=(const image& source) const { return !operator==(source); } image& image::operator=(const image& source) { + if(this == &source) return *this; free(); width = source.width; @@ -49,6 +50,7 @@ image& image::operator=(const image& source) { } image& image::operator=(image&& source) { + if(this == &source) return *this; free(); width = source.width; diff --git a/nall/vector.hpp b/nall/vector.hpp index 2e7c0f2e..99859b70 100644 --- a/nall/vector.hpp +++ b/nall/vector.hpp @@ -75,6 +75,11 @@ public: objectsize = size; } + void reallocate(unsigned size, T value = T()) { + reset(); + resize(size, value); + } + template void prepend(const T& data, Args&&... args) { prepend(std::forward(args)...); prepend(data); diff --git a/ruby/audio/directsound.cpp b/ruby/audio/directsound.cpp index 9da869bc..5465958b 100644 --- a/ruby/audio/directsound.cpp +++ b/ruby/audio/directsound.cpp @@ -1,39 +1,42 @@ -/* - audio.directsound (2007-12-26) - author: byuu -*/ - #include namespace ruby { -class pAudioDS { -public: - LPDIRECTSOUND ds; - LPDIRECTSOUNDBUFFER dsb_p, dsb_b; +struct pAudioDS { + LPDIRECTSOUND ds = nullptr; + LPDIRECTSOUNDBUFFER dsb_p = nullptr; + LPDIRECTSOUNDBUFFER dsb_b = nullptr; DSBUFFERDESC dsbd; WAVEFORMATEX wfx; struct { - unsigned rings; - unsigned latency; + unsigned rings = 0; + unsigned latency = 0; - uint32_t* buffer; - unsigned bufferoffset; + uint32_t* buffer = nullptr; + unsigned bufferoffset = 0; - unsigned readring; - unsigned writering; - int distance; + unsigned readring = 0; + unsigned writering = 0; + int distance = 0; } device; struct { - HWND handle; - bool synchronize; - unsigned frequency; - unsigned latency; + HWND handle = nullptr; + bool synchronize = false; + unsigned frequency = 22050; + unsigned latency = 120; } settings; - bool cap(const string& name) { + pAudioDS() { + settings.handle = GetDesktopWindow(); + } + + ~pAudioDS() { + term(); + } + + auto cap(const string& name) -> bool { if(name == Audio::Handle) return true; if(name == Audio::Synchronize) return true; if(name == Audio::Frequency) return true; @@ -41,34 +44,34 @@ public: return false; } - any get(const string& name) { + auto get(const string& name) -> any { if(name == Audio::Handle) return (uintptr_t)settings.handle; if(name == Audio::Synchronize) return settings.synchronize; if(name == Audio::Frequency) return settings.frequency; if(name == Audio::Latency) return settings.latency; - return false; + return {}; } - bool set(const string& name, const any& value) { - if(name == Audio::Handle) { - settings.handle = (HWND)any_cast(value); + auto set(const string& name, const any& value) -> bool { + if(name == Audio::Handle && value.is()) { + settings.handle = (HWND)value.get(); return true; } - if(name == Audio::Synchronize) { - settings.synchronize = any_cast(value); + if(name == Audio::Synchronize && value.is()) { + settings.synchronize = value.get(); if(ds) clear(); return true; } - if(name == Audio::Frequency) { - settings.frequency = any_cast(value); + if(name == Audio::Frequency && value.is()) { + settings.frequency = value.get(); if(ds) init(); return true; } - if(name == Audio::Latency) { - settings.latency = any_cast(value); + if(name == Audio::Latency && value.is()) { + settings.latency = value.get(); if(ds) init(); return true; } @@ -76,7 +79,7 @@ public: return false; } - void sample(uint16_t left, uint16_t right) { + auto sample(uint16_t left, uint16_t right) -> void { device.buffer[device.bufferoffset++] = left + (right << 16); if(device.bufferoffset < device.latency) return; device.bufferoffset = 0; @@ -113,7 +116,7 @@ public: } } - void clear() { + auto clear() -> void { device.readring = 0; device.writering = device.rings - 1; device.distance = device.rings - 1; @@ -134,7 +137,7 @@ public: dsb_b->Play(0, 0, DSBPLAY_LOOPING); } - bool init() { + auto init() -> bool { term(); device.rings = 8; @@ -175,7 +178,7 @@ public: return true; } - void term() { + auto term() -> void { if(device.buffer) { delete[] device.buffer; device.buffer = 0; @@ -185,23 +188,6 @@ public: if(dsb_p) { dsb_p->Stop(); dsb_p->Release(); dsb_p = 0; } if(ds) { ds->Release(); ds = 0; } } - - pAudioDS() { - ds = 0; - dsb_p = 0; - dsb_b = 0; - - device.buffer = 0; - device.bufferoffset = 0; - device.readring = 0; - device.writering = 0; - device.distance = 0; - - settings.handle = GetDesktopWindow(); - settings.synchronize = false; - settings.frequency = 22050; - settings.latency = 120; - } }; DeclareAudio(DS) diff --git a/ruby/audio/xaudio2.cpp b/ruby/audio/xaudio2.cpp index 9ee6ef54..fbf7f937 100644 --- a/ruby/audio/xaudio2.cpp +++ b/ruby/audio/xaudio2.cpp @@ -3,11 +3,10 @@ namespace ruby { -class pAudioXAudio2: public IXAudio2VoiceCallback { -public: - IXAudio2* pXAudio2; - IXAudio2MasteringVoice* pMasterVoice; - IXAudio2SourceVoice* pSourceVoice; +struct pAudioXAudio2 : public IXAudio2VoiceCallback { + IXAudio2* pXAudio2 = nullptr; + IXAudio2MasteringVoice* pMasterVoice = nullptr; + IXAudio2SourceVoice* pSourceVoice = nullptr; //inherited from IXAudio2VoiceCallback STDMETHODIMP_(void) OnBufferStart(void* pBufferContext){} @@ -18,51 +17,55 @@ public: STDMETHODIMP_(void) OnVoiceProcessingPassStart(UINT32 BytesRequired) {} struct { - unsigned buffers; - unsigned latency; + unsigned buffers = 0; + unsigned latency = 0; - uint32_t* buffer; - unsigned bufferoffset; + uint32_t* buffer = nullptr; + unsigned bufferoffset = 0; - volatile long submitbuffers; - unsigned writebuffer; + volatile long submitbuffers = 0; + unsigned writebuffer = 0; } device; struct { - bool synchronize; - unsigned frequency; - unsigned latency; + bool synchronize = false; + unsigned frequency = 22050; + unsigned latency = 120; } settings; - bool cap(const string& name) { + ~pAudioXAudio2() { + term(); + } + + auto cap(const string& name) -> bool { if(name == Audio::Synchronize) return true; if(name == Audio::Frequency) return true; if(name == Audio::Latency) return true; return false; } - any get(const string& name) { + auto get(const string& name) -> any { if(name == Audio::Synchronize) return settings.synchronize; if(name == Audio::Frequency) return settings.frequency; if(name == Audio::Latency) return settings.latency; - return false; + return {}; } - bool set(const string& name, const any& value) { - if(name == Audio::Synchronize) { - settings.synchronize = any_cast(value); + auto set(const string& name, const any& value) -> bool { + if(name == Audio::Synchronize && value.is()) { + settings.synchronize = value.get(); if(pXAudio2) clear(); return true; } - if(name == Audio::Frequency) { - settings.frequency = any_cast(value); + if(name == Audio::Frequency && value.is()) { + settings.frequency = value.get(); if(pXAudio2) init(); return true; } - if(name == Audio::Latency) { - settings.latency = any_cast(value); + if(name == Audio::Latency && value.is()) { + settings.latency = value.get(); if(pXAudio2) init(); return true; } @@ -70,7 +73,7 @@ public: return false; } - void pushbuffer(unsigned bytes, uint32_t* pAudioData) { + auto pushbuffer(unsigned bytes, uint32_t* pAudioData) -> void { XAUDIO2_BUFFER xa2buffer = {0}; xa2buffer.AudioBytes = bytes; xa2buffer.pAudioData = reinterpret_cast(pAudioData); @@ -79,7 +82,7 @@ public: pSourceVoice->SubmitSourceBuffer(&xa2buffer); } - void sample(uint16_t left, uint16_t right) { + auto sample(uint16_t left, uint16_t right) -> void { device.buffer[device.writebuffer * device.latency + device.bufferoffset++] = left + (right << 16); if(device.bufferoffset < device.latency) return; device.bufferoffset = 0; @@ -100,7 +103,7 @@ public: device.writebuffer = (device.writebuffer + 1) % device.buffers; } - void clear() { + auto clear() -> void { if(!pSourceVoice) return; pSourceVoice->Stop(0); pSourceVoice->FlushSourceBuffers(); //calls OnBufferEnd for all currently submitted buffers @@ -113,7 +116,7 @@ public: pSourceVoice->Start(0); } - bool init() { + auto init() -> bool { term(); device.buffers = 8; @@ -160,7 +163,7 @@ public: return true; } - void term() { + auto term() -> void { if(pSourceVoice) { pSourceVoice->Stop(0); pSourceVoice->DestroyVoice(); @@ -183,21 +186,6 @@ public: STDMETHODIMP_(void) OnBufferEnd(void* pBufferContext) { InterlockedDecrement(&device.submitbuffers); } - - pAudioXAudio2() { - pXAudio2 = nullptr; - pMasterVoice = nullptr; - pSourceVoice = nullptr; - - device.buffer = nullptr; - device.bufferoffset = 0; - device.submitbuffers = 0; - device.writebuffer = 0; - - settings.synchronize = false; - settings.frequency = 22050; - settings.latency = 120; - } }; DeclareAudio(XAudio2) diff --git a/ruby/input/joypad/directinput.cpp b/ruby/input/joypad/directinput.cpp index e61b0359..2c04c6a2 100644 --- a/ruby/input/joypad/directinput.cpp +++ b/ruby/input/joypad/directinput.cpp @@ -3,13 +3,13 @@ namespace ruby { -BOOL CALLBACK DirectInput_EnumJoypadsCallback(const DIDEVICEINSTANCE* instance, void* p); -BOOL CALLBACK DirectInput_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p); -BOOL CALLBACK DirectInput_EnumJoypadEffectsCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p); +auto CALLBACK DirectInput_EnumJoypadsCallback(const DIDEVICEINSTANCE* instance, void* p) -> BOOL; +auto CALLBACK DirectInput_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) -> BOOL; +auto CALLBACK DirectInput_EnumJoypadEffectsCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) -> BOOL; struct InputJoypadDirectInput { struct Joypad { - HID::Joypad hid; + shared_pointer hid{new HID::Joypad}; LPDIRECTINPUTDEVICE8 device = nullptr; LPDIRECTINPUTEFFECT effect = nullptr; @@ -27,14 +27,14 @@ struct InputJoypadDirectInput { bool xinputAvailable = false; unsigned effects = 0; - void assign(HID::Joypad& hid, unsigned groupID, unsigned inputID, int16_t value) { - auto& group = hid.group[groupID]; - if(group.input[inputID].value == value) return; - if(input.onChange) input.onChange(hid, groupID, inputID, group.input[inputID].value, value); - group.input[inputID].value = value; + auto assign(shared_pointer hid, unsigned groupID, unsigned inputID, int16_t value) -> void { + auto& group = hid->group(groupID); + if(group.input(inputID).value() == value) return; + if(input.onChange) input.onChange(hid, groupID, inputID, group.input(inputID).value(), value); + group.input(inputID).setValue(value); } - void poll(vector& devices) { + auto poll(vector>& devices) -> void { for(auto& jp : joypads) { if(FAILED(jp.device->Poll())) jp.device->Acquire(); @@ -68,13 +68,13 @@ struct InputJoypadDirectInput { assign(jp.hid, HID::Joypad::GroupID::Button, n, (bool)state.rgbButtons[n]); } - devices.append(&jp.hid); + devices.append(jp.hid); } } - bool rumble(uint64_t id, bool enable) { + auto rumble(uint64_t id, bool enable) -> bool { for(auto& jp : joypads) { - if(jp.hid.id != id) continue; + if(jp.hid->id() != id) continue; if(jp.effect == nullptr) continue; if(enable) jp.effect->Start(1, 0); @@ -85,7 +85,7 @@ struct InputJoypadDirectInput { return false; } - bool init(uintptr_t handle, LPDIRECTINPUT8 context, bool xinputAvailable) { + auto init(uintptr_t handle, LPDIRECTINPUT8 context, bool xinputAvailable) -> bool { this->handle = handle; this->context = context; this->xinputAvailable = xinputAvailable; @@ -93,7 +93,7 @@ struct InputJoypadDirectInput { return true; } - void term() { + auto term() -> void { for(auto& jp : joypads) { jp.device->Unacquire(); if(jp.effect) jp.effect->Release(); @@ -103,7 +103,7 @@ struct InputJoypadDirectInput { context = nullptr; } - bool initJoypad(const DIDEVICEINSTANCE* instance) { + auto initJoypad(const DIDEVICEINSTANCE* instance) -> bool { Joypad jp; jp.vendorID = instance->guidProduct.Data1 >> 0; jp.productID = instance->guidProduct.Data1 >> 16; @@ -127,7 +127,7 @@ struct InputJoypadDirectInput { effects = 0; device->EnumObjects(DirectInput_EnumJoypadAxesCallback, (void*)this, DIDFT_ABSAXIS); device->EnumObjects(DirectInput_EnumJoypadEffectsCallback, (void*)this, DIDFT_FFACTUATOR); - jp.hid.rumble = effects > 0; + jp.hid->setRumble(effects > 0); DIPROPGUIDANDPATH property; memset(&property, 0, sizeof(DIPROPGUIDANDPATH)); @@ -138,9 +138,9 @@ struct InputJoypadDirectInput { device->GetProperty(DIPROP_GUIDANDPATH, &property.diph); string devicePath = (const char*)utf8_t(property.wszPath); jp.pathID = Hash::CRC32(devicePath.data(), devicePath.size()).value(); - jp.hid.id = (uint64_t)jp.pathID << 32 | jp.vendorID << 16 | jp.productID << 0; + jp.hid->setID((uint64_t)jp.pathID << 32 | jp.vendorID << 16 | jp.productID << 0); - if(jp.hid.rumble) { + if(jp.hid->rumble()) { //disable auto-centering spring for rumble support DIPROPDWORD property; memset(&property, 0, sizeof(DIPROPDWORD)); @@ -174,15 +174,15 @@ struct InputJoypadDirectInput { device->CreateEffect(GUID_ConstantForce, &effect, &jp.effect, NULL); } - for(unsigned n = 0; n < 6; n++) jp.hid.axis().append({n}); - for(unsigned n = 0; n < 8; n++) jp.hid.hat().append({n}); - for(unsigned n = 0; n < 128; n++) jp.hid.button().append({n}); + for(unsigned n = 0; n < 6; n++) jp.hid->axes().append(n); + for(unsigned n = 0; n < 8; n++) jp.hid->hats().append(n); + for(unsigned n = 0; n < 128; n++) jp.hid->buttons().append(n); joypads.append(jp); return DIENUM_CONTINUE; } - bool initAxis(const DIDEVICEOBJECTINSTANCE* instance) { + auto initAxis(const DIDEVICEOBJECTINSTANCE* instance) -> bool { DIPROPRANGE range; memset(&range, 0, sizeof(DIPROPRANGE)); range.diph.dwSize = sizeof(DIPROPRANGE); @@ -195,21 +195,21 @@ struct InputJoypadDirectInput { return DIENUM_CONTINUE; } - bool initEffect(const DIDEVICEOBJECTINSTANCE* instance) { + auto initEffect(const DIDEVICEOBJECTINSTANCE* instance) -> bool { effects++; return DIENUM_CONTINUE; } }; -BOOL CALLBACK DirectInput_EnumJoypadsCallback(const DIDEVICEINSTANCE* instance, void* p) { +auto CALLBACK DirectInput_EnumJoypadsCallback(const DIDEVICEINSTANCE* instance, void* p) -> BOOL { return ((InputJoypadDirectInput*)p)->initJoypad(instance); } -BOOL CALLBACK DirectInput_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) { +auto CALLBACK DirectInput_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) -> BOOL { return ((InputJoypadDirectInput*)p)->initAxis(instance); } -BOOL CALLBACK DirectInput_EnumJoypadEffectsCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) { +auto CALLBACK DirectInput_EnumJoypadEffectsCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) -> BOOL { return ((InputJoypadDirectInput*)p)->initEffect(instance); } diff --git a/ruby/input/joypad/xinput.cpp b/ruby/input/joypad/xinput.cpp index bc058066..08e0c762 100644 --- a/ruby/input/joypad/xinput.cpp +++ b/ruby/input/joypad/xinput.cpp @@ -27,19 +27,19 @@ struct InputJoypadXInput { pXInputSetState XInputSetState = nullptr; struct Joypad { - HID::Joypad hid; - unsigned id; + shared_pointer hid{new HID::Joypad}; + unsigned id = 0; }; vector joypads; - void assign(HID::Joypad& hid, unsigned groupID, unsigned inputID, int16_t value) { - auto& group = hid.group[groupID]; - if(group.input[inputID].value == value) return; - if(input.onChange) input.onChange(hid, groupID, inputID, group.input[inputID].value, value); - group.input[inputID].value = value; + auto assign(shared_pointer hid, unsigned groupID, unsigned inputID, int16_t value) -> void { + auto& group = hid->group(groupID); + if(group.input(inputID).value() == value) return; + if(input.onChange) input.onChange(hid, groupID, inputID, group.input(inputID).value(), value); + group.input(inputID).setValue(value); } - void poll(vector& devices) { + auto poll(vector>& devices) -> void { for(auto& jp : joypads) { XINPUT_STATE state; if(XInputGetStateEx(jp.id, &state) != ERROR_SUCCESS) continue; @@ -83,13 +83,13 @@ struct InputJoypadXInput { assign(jp.hid, HID::Joypad::GroupID::Button, 9, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB)); assign(jp.hid, HID::Joypad::GroupID::Button, 10, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_GUIDE)); - devices.append(&jp.hid); + devices.append(jp.hid); } } - bool rumble(uint64_t id, bool enable) { + auto rumble(uint64_t id, bool enable) -> bool { for(auto& jp : joypads) { - if(jp.hid.id != id) continue; + if(jp.hid->id() != id) continue; XINPUT_VIBRATION vibration; memset(&vibration, 0, sizeof(XINPUT_VIBRATION)); @@ -102,7 +102,7 @@ struct InputJoypadXInput { return false; } - bool init() { + auto init() -> bool { if(!libxinput) libxinput = LoadLibraryA("xinput1_3.dll"); if(!libxinput) return false; @@ -118,30 +118,31 @@ struct InputJoypadXInput { for(unsigned id = 0; id < 4; id++) { Joypad jp; jp.id = id; - jp.hid.id = (uint64_t)(1 + id) << 32 | 0x045e << 16 | 0x028e << 0; //Xbox 360 Player# + VendorID + ProductID + jp.hid->setID((uint64_t)(1 + id) << 32 | 0x045e << 16 | 0x028e << 0); //Xbox 360 Player# + VendorID + ProductID + jp.hid->setRumble(true); - jp.hid.axis().append({"LeftThumbX"}); - jp.hid.axis().append({"LeftThumbY"}); - jp.hid.axis().append({"RightThumbX"}); - jp.hid.axis().append({"RightThumbY"}); + jp.hid->axes().append("LeftThumbX"); + jp.hid->axes().append("LeftThumbY"); + jp.hid->axes().append("RightThumbX"); + jp.hid->axes().append("RightThumbY"); - jp.hid.hat().append({"HatX"}); - jp.hid.hat().append({"HatY"}); + jp.hid->hats().append("HatX"); + jp.hid->hats().append("HatY"); - jp.hid.trigger().append({"LeftTrigger"}); - jp.hid.trigger().append({"RightTrigger"}); + jp.hid->triggers().append("LeftTrigger"); + jp.hid->triggers().append("RightTrigger"); - jp.hid.button().append({"A"}); - jp.hid.button().append({"B"}); - jp.hid.button().append({"X"}); - jp.hid.button().append({"Y"}); - jp.hid.button().append({"Back"}); - jp.hid.button().append({"Start"}); - jp.hid.button().append({"LeftShoulder"}); - jp.hid.button().append({"RightShoulder"}); - jp.hid.button().append({"LeftThumb"}); - jp.hid.button().append({"RightThumb"}); - jp.hid.button().append({"Guide"}); + jp.hid->buttons().append("A"); + jp.hid->buttons().append("B"); + jp.hid->buttons().append("X"); + jp.hid->buttons().append("Y"); + jp.hid->buttons().append("Back"); + jp.hid->buttons().append("Start"); + jp.hid->buttons().append("LeftShoulder"); + jp.hid->buttons().append("RightShoulder"); + jp.hid->buttons().append("LeftThumb"); + jp.hid->buttons().append("RightThumb"); + jp.hid->buttons().append("Guide"); joypads.append(jp); } @@ -149,7 +150,7 @@ struct InputJoypadXInput { return true; } - void term() { + auto term() -> void { if(!libxinput) return; FreeLibrary(libxinput); diff --git a/ruby/input/keyboard/rawinput.cpp b/ruby/input/keyboard/rawinput.cpp index ace90547..df17e3a2 100644 --- a/ruby/input/keyboard/rawinput.cpp +++ b/ruby/input/keyboard/rawinput.cpp @@ -13,10 +13,10 @@ struct InputKeyboardRawInput { vector keys; struct Keyboard { - HID::Keyboard hid; + shared_pointer hid{new HID::Keyboard}; } kb; - void update(RAWINPUT* input) { + auto update(RAWINPUT* input) -> void { unsigned code = input->data.keyboard.MakeCode; unsigned flag = input->data.keyboard.Flags; @@ -26,19 +26,19 @@ struct InputKeyboardRawInput { } } - void assign(unsigned inputID, bool value) { - auto& group = kb.hid.group[HID::Keyboard::GroupID::Button]; - if(group.input[inputID].value == value) return; - if(input.onChange) input.onChange(kb.hid, HID::Keyboard::GroupID::Button, inputID, group.input[inputID].value, value); - group.input[inputID].value = value; + auto assign(unsigned inputID, bool value) -> void { + auto& group = kb.hid->buttons(); + if(group.input(inputID).value() == value) return; + if(input.onChange) input.onChange(kb.hid, HID::Keyboard::GroupID::Button, inputID, group.input(inputID).value(), value); + group.input(inputID).setValue(value); } - void poll(vector& devices) { + auto poll(vector>& devices) -> void { for(unsigned n = 0; n < keys.size(); n++) assign(n, keys[n].value); - devices.append(&kb.hid); + devices.append(kb.hid); } - bool init() { + auto init() -> bool { rawinput.updateKeyboard = {&InputKeyboardRawInput::update, this}; //Pause sends 0x001d,4 + 0x0045,0; NumLock sends only 0x0045,0 @@ -163,8 +163,8 @@ struct InputKeyboardRawInput { keys.append({0x005c, 2, "RightSuper"}); keys.append({0x005d, 2, "Menu"}); - kb.hid.id = 1; - for(auto& key : keys) kb.hid.button().append({key.name}); + kb.hid->setID(1); + for(auto& key : keys) kb.hid->buttons().append(key.name); return true; } diff --git a/ruby/input/mouse/rawinput.cpp b/ruby/input/mouse/rawinput.cpp index e118753a..b7ad9a59 100644 --- a/ruby/input/mouse/rawinput.cpp +++ b/ruby/input/mouse/rawinput.cpp @@ -8,7 +8,7 @@ struct InputMouseRawInput { bool mouseAcquired = false; struct Mouse { - HID::Mouse hid; + shared_pointer hid{new HID::Mouse}; signed relativeX = 0; signed relativeY = 0; @@ -16,7 +16,7 @@ struct InputMouseRawInput { bool buttons[5] = {0}; } ms; - bool acquire() { + auto acquire() -> bool { if(mouseAcquired == false) { mouseAcquired = true; ShowCursor(false); @@ -24,17 +24,17 @@ struct InputMouseRawInput { return true; } - bool unacquire() { + auto unacquire() -> bool { if(mouseAcquired == true) { mouseAcquired = false; ReleaseCapture(); - ClipCursor(NULL); + ClipCursor(nullptr); ShowCursor(true); } return true; } - bool acquired() { + auto acquired() -> bool { if(mouseAcquired == true) { SetFocus((HWND)handle); SetCapture((HWND)handle); @@ -45,7 +45,7 @@ struct InputMouseRawInput { return GetCapture() == (HWND)handle; } - void update(RAWINPUT* input) { + auto update(RAWINPUT* input) -> void { if((input->data.mouse.usFlags & 1) == MOUSE_MOVE_RELATIVE) { ms.relativeX += input->data.mouse.lLastX; ms.relativeY += input->data.mouse.lLastY; @@ -67,14 +67,14 @@ struct InputMouseRawInput { if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP ) ms.buttons[4] = 0; } - void assign(unsigned groupID, unsigned inputID, int16_t value) { - auto& group = ms.hid.group[groupID]; - if(group.input[inputID].value == value) return; - if(input.onChange) input.onChange(ms.hid, groupID, inputID, group.input[inputID].value, value); - group.input[inputID].value = value; + auto assign(unsigned groupID, unsigned inputID, int16_t value) -> void { + auto& group = ms.hid->group(groupID); + if(group.input(inputID).value() == value) return; + if(input.onChange) input.onChange(ms.hid, groupID, inputID, group.input(inputID).value(), value); + group.input(inputID).setValue(value); } - void poll(vector& devices) { + auto poll(vector>& devices) -> void { assign(HID::Mouse::GroupID::Axis, 0, ms.relativeX); assign(HID::Mouse::GroupID::Axis, 1, ms.relativeY); assign(HID::Mouse::GroupID::Axis, 2, ms.relativeZ); @@ -91,29 +91,29 @@ struct InputMouseRawInput { ms.relativeY = 0; ms.relativeZ = 0; - devices.append(&ms.hid); + devices.append(ms.hid); } - bool init(uintptr_t handle) { + auto init(uintptr_t handle) -> bool { this->handle = handle; - ms.hid.id = 2; + ms.hid->setID(2); - ms.hid.axis().append({"X"}); - ms.hid.axis().append({"Y"}); - ms.hid.axis().append({"Z"}); + ms.hid->axes().append("X"); + ms.hid->axes().append("Y"); + ms.hid->axes().append("Z"); - ms.hid.button().append({"Left"}); - ms.hid.button().append({"Middle"}); - ms.hid.button().append({"Right"}); - ms.hid.button().append({"Up"}); - ms.hid.button().append({"Down"}); + ms.hid->buttons().append("Left"); + ms.hid->buttons().append("Middle"); + ms.hid->buttons().append("Right"); + ms.hid->buttons().append("Up"); + ms.hid->buttons().append("Down"); rawinput.updateMouse = {&InputMouseRawInput::update, this}; return true; } - void term() { + auto term() -> void { unacquire(); } }; diff --git a/ruby/input/shared/rawinput.cpp b/ruby/input/shared/rawinput.cpp index 063384ef..38a522d2 100644 --- a/ruby/input/shared/rawinput.cpp +++ b/ruby/input/shared/rawinput.cpp @@ -3,18 +3,18 @@ namespace ruby { -LRESULT CALLBACK RawInputWindowProc(HWND, UINT, WPARAM, LPARAM); +auto CALLBACK RawInputWindowProc(HWND, UINT, WPARAM, LPARAM) -> LRESULT; struct RawInput { - HANDLE mutex; - HWND hwnd; + HANDLE mutex = nullptr; + HWND hwnd = nullptr; bool ready = false; bool initialized = false; function updateKeyboard; function updateMouse; struct Device { - HANDLE handle; + HANDLE handle = nullptr; string path; enum class Type : unsigned { Keyboard, Mouse, Joypad } type; uint16_t vendorID = 0; @@ -23,14 +23,14 @@ struct RawInput { }; vector devices; - maybe find(uint16_t vendorID, uint16_t productID) { + auto find(uint16_t vendorID, uint16_t productID) -> maybe { for(auto& device : devices) { if(device.vendorID == vendorID && device.productID == productID) return device; } return nothing; } - void scanDevices() { + auto scanDevices() -> void { devices.reset(); unsigned deviceCount = 0; @@ -79,7 +79,7 @@ struct RawInput { delete[] list; } - LRESULT windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + auto windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT { if(msg != WM_INPUT) return DefWindowProc(hwnd, msg, wparam, lparam); unsigned size = 0; @@ -102,7 +102,7 @@ struct RawInput { return result; } - void main() { + auto main() -> void { WNDCLASS wc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; @@ -148,12 +148,12 @@ struct RawInput { static RawInput rawinput; -DWORD WINAPI RawInputThreadProc(void*) { +auto WINAPI RawInputThreadProc(void*) -> DWORD { rawinput.main(); return 0; } -LRESULT CALLBACK RawInputWindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { +auto CALLBACK RawInputWindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT { return rawinput.windowProc(hwnd, msg, wparam, lparam); } diff --git a/ruby/input/windows.cpp b/ruby/input/windows.cpp index 3833b3b5..e3aef367 100644 --- a/ruby/input/windows.cpp +++ b/ruby/input/windows.cpp @@ -22,7 +22,11 @@ struct pInputWindows { uintptr_t handle = 0; } settings; - bool cap(const string& name) { + ~pInputWindows() { + term(); + } + + auto cap(const string& name) -> bool { if(name == Input::Handle) return true; if(name == Input::KeyboardSupport) return true; if(name == Input::MouseSupport) return true; @@ -31,33 +35,33 @@ struct pInputWindows { return false; } - any get(const string& name) { + auto get(const string& name) -> any { if(name == Input::Handle) return (uintptr_t)settings.handle; - return false; + return {}; } - bool set(const string& name, const any& value) { - if(name == Input::Handle) { - settings.handle = any_cast(value); + auto set(const string& name, const any& value) -> bool { + if(name == Input::Handle && value.is()) { + settings.handle = value.get(); return true; } return false; } - bool acquire() { + auto acquire() -> bool { return rawinputMouse.acquire(); } - bool unacquire() { + auto unacquire() -> bool { return rawinputMouse.unacquire(); } - bool acquired() { + auto acquired() -> bool { return rawinputMouse.acquired(); } - vector poll() { - vector devices; + auto poll() -> vector> { + vector> devices; rawinputKeyboard.poll(devices); rawinputMouse.poll(devices); xinput.poll(devices); @@ -65,13 +69,13 @@ struct pInputWindows { return devices; } - bool rumble(uint64_t id, bool enable) { + auto rumble(uint64_t id, bool enable) -> bool { if(xinput.rumble(id, enable)) return true; if(directinput.rumble(id, enable)) return true; return false; } - bool init() { + auto init() -> bool { if(rawinput.initialized == false) { rawinput.initialized = true; rawinput.mutex = CreateMutex(NULL, FALSE, NULL); @@ -94,7 +98,7 @@ struct pInputWindows { return true; } - void term() { + auto term() -> void { rawinputKeyboard.term(); rawinputMouse.term(); xinput.term(); diff --git a/ruby/video/direct3d.cpp b/ruby/video/direct3d.cpp index 3af018d8..9cb933ec 100644 --- a/ruby/video/direct3d.cpp +++ b/ruby/video/direct3d.cpp @@ -11,24 +11,24 @@ typedef HRESULT (__stdcall* TextureProc)(LPDIRECT3DDEVICE9, LPCTSTR, LPDIRECT3DT namespace ruby { -class pVideoD3D { -public: - LPDIRECT3D9 lpd3d; - LPDIRECT3DDEVICE9 device; - LPDIRECT3DVERTEXBUFFER9 vertex_buffer; - LPDIRECT3DVERTEXBUFFER9* vertex_ptr; +struct pVideoD3D { + LPDIRECT3D9 lpd3d = nullptr; + LPDIRECT3DDEVICE9 device = nullptr; + LPDIRECT3DVERTEXBUFFER9 vertex_buffer = nullptr; + LPDIRECT3DVERTEXBUFFER9* vertex_ptr = nullptr; D3DPRESENT_PARAMETERS presentation; D3DSURFACE_DESC d3dsd; D3DLOCKED_RECT d3dlr; D3DRASTER_STATUS d3drs; D3DCAPS9 d3dcaps; - LPDIRECT3DTEXTURE9 texture; - LPDIRECT3DSURFACE9 surface; - LPD3DXEFFECT effect; + LPDIRECT3DTEXTURE9 texture = nullptr; + LPDIRECT3DSURFACE9 surface = nullptr; + LPD3DXEFFECT effect = nullptr; string shader_source_markup; - bool lost; - unsigned iwidth, iheight; + bool lost = true; + unsigned iwidth; + unsigned iheight; struct d3dvertex { float x, y, z, rhw; //screen coords @@ -48,9 +48,9 @@ public: } caps; struct { - HWND handle; - bool synchronize; - unsigned filter; + HWND handle = nullptr; + bool synchronize = false; + unsigned filter = Video::FilterLinear; unsigned width; unsigned height; @@ -61,7 +61,11 @@ public: unsigned height; } state; - bool cap(const string& name) { + ~pVideoD3D() { + term(); + } + + auto cap(const string& name) -> bool { if(name == Video::Handle) return true; if(name == Video::Synchronize) return true; if(name == Video::Filter) return true; @@ -69,40 +73,40 @@ public: return false; } - any get(const string& name) { + auto get(const string& name) -> any { if(name == Video::Handle) return (uintptr_t)settings.handle; if(name == Video::Synchronize) return settings.synchronize; if(name == Video::Filter) return settings.filter; - return false; + return {}; } - bool set(const string& name, const any& value) { - if(name == Video::Handle) { - settings.handle = (HWND)any_cast(value); + auto set(const string& name, const any& value) -> bool { + if(name == Video::Handle && value.is()) { + settings.handle = (HWND)value.get(); return true; } - if(name == Video::Synchronize) { - settings.synchronize = any_cast(value); + if(name == Video::Synchronize && value.is()) { + settings.synchronize = value.get(); return true; } - if(name == Video::Filter) { - settings.filter = any_cast(value); + if(name == Video::Filter && value.is()) { + settings.filter = value.get(); if(lpd3d) update_filter(); return true; } - if(name == Video::Shader) { + if(name == Video::Shader && value.is()) { return false; - set_shader(any_cast(value)); - return true; + //set_shader(value.get()); + //return true; } return false; } - bool recover() { + auto recover() -> bool { if(!device) return false; if(lost) { @@ -142,7 +146,7 @@ public: return true; } - unsigned rounded_power_of_two(unsigned n) { + auto rounded_power_of_two(unsigned n) -> unsigned { n--; n |= n >> 1; n |= n >> 2; @@ -152,7 +156,7 @@ public: return n + 1; } - void resize(unsigned width, unsigned height) { + auto resize(unsigned width, unsigned height) -> void { if(iwidth >= width && iheight >= height) return; iwidth = rounded_power_of_two(max(width, iwidth )); @@ -167,7 +171,7 @@ public: device->CreateTexture(iwidth, iheight, 1, flags.t_usage, D3DFMT_X8R8G8B8, (D3DPOOL)flags.t_pool, &texture, NULL); } - void update_filter() { + auto update_filter() -> void { if(!device) return; if(lost && !recover()) return; @@ -188,11 +192,11 @@ public: // // (x,y) screen coords, in pixels // (u,v) texture coords, betweeen 0.0 (top, left) to 1.0 (bottom, right) - void set_vertex( + auto set_vertex( uint32_t px, uint32_t py, uint32_t pw, uint32_t ph, uint32_t tw, uint32_t th, uint32_t x, uint32_t y, uint32_t w, uint32_t h - ) { + ) -> void { d3dvertex vertex[4]; vertex[0].x = vertex[2].x = (double)(x - 0.5); vertex[1].x = vertex[3].x = (double)(x + w - 0.5); @@ -217,7 +221,7 @@ public: device->SetStreamSource(0, vertex_buffer, 0, sizeof(d3dvertex)); } - void clear() { + auto clear() -> void { if(lost && !recover()) return; texture->GetLevelDesc(0, &d3dsd); @@ -236,7 +240,7 @@ public: } } - bool lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) { + auto lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) -> bool { if(lost && !recover()) return false; if(width != settings.width || height != settings.height) { @@ -251,13 +255,13 @@ public: return data = (uint32_t*)d3dlr.pBits; } - void unlock() { + auto unlock() -> void { surface->UnlockRect(); surface->Release(); surface = nullptr; } - void refresh() { + auto refresh() -> void { if(lost && !recover()) return; RECT rd, rs; //dest, source rectangles @@ -333,7 +337,7 @@ public: if(device->Present(0, 0, 0, 0) == D3DERR_DEVICELOST) lost = true; } - void set_shader(const char* source) { + auto set_shader(const char* source) -> void { if(!caps.shader) return; if(effect) { @@ -347,9 +351,9 @@ public: } shader_source_markup = source; - XML::Document document(shader_source_markup); - bool is_hlsl = document["shader"]["language"].data == "HLSL"; - string shader_source = document["shader"]["source"].data; + auto document = BML::unserialize(shader_source_markup); + bool is_hlsl = document["shader"]["language"].text() == "HLSL"; + string shader_source = document["shader"]["source"].text(); if(shader_source == "") return; HMODULE d3dx; @@ -373,7 +377,7 @@ public: effect->SetTechnique(hTech); } - bool init() { + auto init() -> bool { term(); RECT rd; @@ -428,32 +432,18 @@ public: return true; } - void release_resources() { + auto release_resources() -> void { if(effect) { effect->Release(); effect = 0; } if(vertex_buffer) { vertex_buffer->Release(); vertex_buffer = 0; } if(surface) { surface->Release(); surface = 0; } if(texture) { texture->Release(); texture = 0; } } - void term() { + auto term() -> void { release_resources(); if(device) { device->Release(); device = 0; } if(lpd3d) { lpd3d->Release(); lpd3d = 0; } } - - pVideoD3D() { - effect = 0; - vertex_buffer = 0; - surface = 0; - texture = 0; - device = 0; - lpd3d = 0; - lost = true; - - settings.handle = 0; - settings.synchronize = false; - settings.filter = Video::FilterLinear; - } }; DeclareVideo(D3D) diff --git a/ruby/video/directdraw.cpp b/ruby/video/directdraw.cpp index f7731de9..96fc2bb9 100644 --- a/ruby/video/directdraw.cpp +++ b/ruby/video/directdraw.cpp @@ -2,51 +2,56 @@ namespace ruby { -class pVideoDD { -public: - LPDIRECTDRAW lpdd; - LPDIRECTDRAW7 lpdd7; - LPDIRECTDRAWSURFACE7 screen, raster; - LPDIRECTDRAWCLIPPER clipper; +struct pVideoDD { + LPDIRECTDRAW lpdd = nullptr; + LPDIRECTDRAW7 lpdd7 = nullptr; + LPDIRECTDRAWSURFACE7 screen = nullptr; + LPDIRECTDRAWSURFACE7 raster = nullptr; + LPDIRECTDRAWCLIPPER clipper = nullptr; DDSURFACEDESC2 ddsd; DDSCAPS2 ddscaps; - unsigned iwidth, iheight; + unsigned iwidth; + unsigned iheight; struct { - HWND handle; - bool synchronize; + HWND handle = nullptr; + bool synchronize = false; unsigned width; unsigned height; } settings; - bool cap(const string& name) { + ~pVideoDD() { + term(); + } + + auto cap(const string& name) -> bool { if(name == Video::Handle) return true; if(name == Video::Synchronize) return true; return false; } - any get(const string& name) { + auto get(const string& name) -> any { if(name == Video::Handle) return (uintptr_t)settings.handle; if(name == Video::Synchronize) return settings.synchronize; - return false; + return {}; } - bool set(const string& name, const any& value) { - if(name == Video::Handle) { - settings.handle = (HWND)any_cast(value); + auto set(const string& name, const any& value) -> bool { + if(name == Video::Handle && value.is()) { + settings.handle = (HWND)value.get(); return true; } - if(name == Video::Synchronize) { - settings.synchronize = any_cast(value); + if(name == Video::Synchronize && value.is()) { + settings.synchronize = value.get(); return true; } return false; } - void resize(unsigned width, unsigned height) { + auto resize(unsigned width, unsigned height) -> void { if(iwidth >= width && iheight >= height) return; iwidth = max(width, iwidth); @@ -85,7 +90,7 @@ public: if(lpdd7->CreateSurface(&ddsd, &raster, 0) == DD_OK) return clear(); } - void clear() { + auto clear() -> void { DDBLTFX fx; fx.dwSize = sizeof(DDBLTFX); fx.dwFillColor = 0x00000000; @@ -93,7 +98,7 @@ public: raster->Blt(0, 0, 0, DDBLT_WAIT | DDBLT_COLORFILL, &fx); } - bool lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) { + auto lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) -> bool { if(width != settings.width || height != settings.height) { resize(settings.width = width, settings.height = height); } @@ -106,11 +111,11 @@ public: return data = (uint32_t*)ddsd.lpSurface; } - void unlock() { + auto unlock() -> void { raster->Unlock(0); } - void refresh() { + auto refresh() -> void { if(settings.synchronize) { while(true) { BOOL in_vblank; @@ -134,7 +139,7 @@ public: } } - bool init() { + auto init() -> bool { term(); DirectDrawCreate(0, &lpdd, 0); @@ -162,23 +167,13 @@ public: return true; } - void term() { + auto term() -> void { if(clipper) { clipper->Release(); clipper = 0; } if(raster) { raster->Release(); raster = 0; } if(screen) { screen->Release(); screen = 0; } if(lpdd7) { lpdd7->Release(); lpdd7 = 0; } if(lpdd) { lpdd->Release(); lpdd = 0; } } - - pVideoDD() { - lpdd = 0; - lpdd7 = 0; - screen = 0; - raster = 0; - clipper = 0; - - settings.handle = 0; - } }; DeclareVideo(DD) diff --git a/ruby/video/gdi.cpp b/ruby/video/gdi.cpp index ada01b96..5f3631a4 100644 --- a/ruby/video/gdi.cpp +++ b/ruby/video/gdi.cpp @@ -2,40 +2,48 @@ namespace ruby { -class pVideoGDI { -public: - uint32_t* buffer; - HBITMAP bitmap; - HDC bitmapdc; +struct pVideoGDI { + uint32_t* buffer = nullptr; + HBITMAP bitmap = nullptr; + HDC bitmapdc = nullptr; BITMAPINFO bmi; struct { - HWND handle; + HWND handle = nullptr; unsigned width; unsigned height; } settings; - bool cap(const string& name) { + pVideoGDI() { + buffer = (uint32_t*)memory::allocate(1024 * 1024 * sizeof(uint32_t)); + } + + ~pVideoGDI() { + if(buffer) memory::free(buffer); + term(); + } + + auto cap(const string& name) -> bool { if(name == Video::Handle) return true; return false; } - any get(const string& name) { + auto get(const string& name) -> any { if(name == Video::Handle) return (uintptr_t)settings.handle; - return false; + return {}; } - bool set(const string& name, const any& value) { - if(name == Video::Handle) { - settings.handle = (HWND)any_cast(value); + auto set(const string& name, const any& value) -> bool { + if(name == Video::Handle && value.is()) { + settings.handle = (HWND)value.get(); return true; } return false; } - bool lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) { + auto lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) -> bool { settings.width = width; settings.height = height; @@ -43,11 +51,11 @@ public: return data = buffer; } - void unlock() {} + auto unlock() -> void {} - void clear() {} + auto clear() -> void {} - void refresh() { + auto refresh() -> void { RECT rc; GetClientRect(settings.handle, &rc); @@ -57,7 +65,7 @@ public: ReleaseDC(settings.handle, hdc); } - bool init() { + auto init() -> bool { HDC hdc = GetDC(settings.handle); bitmapdc = CreateCompatibleDC(hdc); assert(bitmapdc); @@ -80,19 +88,10 @@ public: return true; } - void term() { + auto term() -> void { DeleteObject(bitmap); DeleteDC(bitmapdc); } - - pVideoGDI() { - buffer = (uint32_t*)malloc(1024 * 1024 * sizeof(uint32_t)); - settings.handle = 0; - } - - ~pVideoGDI() { - if(buffer) free(buffer); - } }; DeclareVideo(GDI) diff --git a/ruby/video/wgl.cpp b/ruby/video/wgl.cpp index a5801f96..ea9e7687 100644 --- a/ruby/video/wgl.cpp +++ b/ruby/video/wgl.cpp @@ -9,19 +9,23 @@ struct pVideoWGL : OpenGL { HGLRC (APIENTRY* wglCreateContextAttribs)(HDC, HGLRC, const int*) = nullptr; BOOL (APIENTRY* wglSwapInterval)(int) = nullptr; - HDC display; - HGLRC wglcontext; - HWND window; - HINSTANCE glwindow; + HDC display = nullptr; + HGLRC wglcontext = nullptr; + HWND window = nullptr; + HINSTANCE glwindow = nullptr; struct { - HWND handle; - bool synchronize; - unsigned filter; + HWND handle = nullptr; + bool synchronize = false; + unsigned filter = Video::FilterNearest; string shader; } settings; - bool cap(const string& name) { + ~pVideoWGL() { + term(); + } + + auto cap(const string& name) -> bool { if(name == Video::Handle) return true; if(name == Video::Synchronize) return true; if(name == Video::Filter) return true; @@ -29,22 +33,22 @@ struct pVideoWGL : OpenGL { return false; } - any get(const string& name) { + auto get(const string& name) -> any { if(name == Video::Handle) return (uintptr_t)settings.handle; if(name == Video::Synchronize) return settings.synchronize; if(name == Video::Filter) return settings.filter; - return false; + return {}; } - bool set(const string& name, const any& value) { - if(name == Video::Handle) { - settings.handle = (HWND)any_cast(value); + auto set(const string& name, const any& value) -> bool { + if(name == Video::Handle && value.is()) { + settings.handle = (HWND)value.get(); return true; } - if(name == Video::Synchronize) { - if(settings.synchronize != any_cast(value)) { - settings.synchronize = any_cast(value); + if(name == Video::Synchronize && value.is()) { + if(settings.synchronize != value.get()) { + settings.synchronize = value.get(); if(wglcontext) { init(); OpenGL::shader(settings.shader); @@ -53,36 +57,36 @@ struct pVideoWGL : OpenGL { } } - if(name == Video::Filter) { - settings.filter = any_cast(value); - if(settings.shader.empty()) OpenGL::filter = settings.filter ? GL_LINEAR : GL_NEAREST; + if(name == Video::Filter && value.is()) { + settings.filter = value.get(); + if(!settings.shader) OpenGL::filter = settings.filter ? GL_LINEAR : GL_NEAREST; return true; } - if(name == Video::Shader) { - settings.shader = any_cast(value); + if(name == Video::Shader && value.is()) { + settings.shader = value.get(); OpenGL::shader(settings.shader); - if(settings.shader.empty()) OpenGL::filter = settings.filter ? GL_LINEAR : GL_NEAREST; + if(!settings.shader) OpenGL::filter = settings.filter ? GL_LINEAR : GL_NEAREST; return true; } return false; } - bool lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) { + auto lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) -> bool { OpenGL::size(width, height); return OpenGL::lock(data, pitch); } - void unlock() { + auto unlock() -> void { } - void clear() { + auto clear() -> void { OpenGL::clear(); SwapBuffers(display); } - void refresh() { + auto refresh() -> void { RECT rc; GetClientRect(settings.handle, &rc); outputWidth = rc.right - rc.left, outputHeight = rc.bottom - rc.top; @@ -90,7 +94,7 @@ struct pVideoWGL : OpenGL { SwapBuffers(display); } - bool init() { + auto init() -> bool { term(); GLuint pixel_format; @@ -133,26 +137,14 @@ struct pVideoWGL : OpenGL { return true; } - void term() { + auto term() -> void { OpenGL::term(); if(wglcontext) { wglDeleteContext(wglcontext); - wglcontext = 0; + wglcontext = nullptr; } } - - pVideoWGL() { - settings.handle = 0; - settings.synchronize = false; - settings.filter = 0; - - window = 0; - wglcontext = 0; - glwindow = 0; - } - - ~pVideoWGL() { term(); } }; DeclareVideo(WGL) diff --git a/target-tomoko/GNUmakefile b/target-tomoko/GNUmakefile index 6bc1e364..7022cab1 100644 --- a/target-tomoko/GNUmakefile +++ b/target-tomoko/GNUmakefile @@ -11,6 +11,7 @@ include gba/GNUmakefile ui_objects := ui-tomoko ui-program ui-configuration ui-input ui_objects += ui-settings ui-tools ui-presentation ui_objects += ruby hiro +ui_objects += $(if $(call streq,$(platform),windows),ui-resource) # platform ifeq ($(platform),windows) @@ -58,6 +59,9 @@ obj/ui-settings.o: $(ui)/settings/settings.cpp $(call rwildcard,$(ui)/) obj/ui-tools.o: $(ui)/tools/tools.cpp $(call rwildcard,$(ui)/) obj/ui-presentation.o: $(ui)/presentation/presentation.cpp $(call rwildcard,$(ui)/) +obj/ui-resource.o: + windres data/resource.rc obj/ui-resource.o + # targets build: $(objects) $(strip $(compiler) -o out/$(name) $(objects) $(link)) diff --git a/target-tomoko/presentation/presentation.cpp b/target-tomoko/presentation/presentation.cpp index 5463839b..36ef6b3c 100644 --- a/target-tomoko/presentation/presentation.cpp +++ b/target-tomoko/presentation/presentation.cpp @@ -31,7 +31,6 @@ Presentation::Presentation() { settingsMenu.setText("Settings"); videoScaleMenu.setText("Video Scale"); - MenuRadioItem::group({videoScaleSmall, videoScaleNormal, videoScaleLarge}); if(config().video.scale == "Small") videoScaleSmall.setChecked(); if(config().video.scale == "Normal") videoScaleNormal.setChecked(); if(config().video.scale == "Large") videoScaleLarge.setChecked(); @@ -52,7 +51,6 @@ Presentation::Presentation() { resizeViewport(); }); videoFilterMenu.setText("Video Filter"); - MenuRadioItem::group({videoFilterNone, videoFilterBlur}); if(config().video.filter == "None") videoFilterNone.setChecked(); if(config().video.filter == "Blur") videoFilterBlur.setChecked(); videoFilterNone.setText("None").onActivate([&] { config().video.filter = "None"; program->updateVideoFilter(); }); @@ -122,16 +120,15 @@ auto Presentation::updateEmulator() -> void { auto& menu = (n == 0 ? inputPort1 : inputPort2); menu.setText(port.name); - vector items; + Group devices; for(auto& device : port.device) { MenuRadioItem item{&menu}; item.setText(device.name).onActivate([=] { emulator->connect(port.id, device.id); }); - items.append(item); + devices.append(item); } - MenuRadioItem::group(items); - if(items.size() > 1) menu.setVisible(); + if(devices.objects() > 1) menu.setVisible(); } systemMenuSeparatorPorts.setVisible(inputPort1.visible() || inputPort2.visible()); diff --git a/target-tomoko/presentation/presentation.hpp b/target-tomoko/presentation/presentation.hpp index 4f7b3f2d..ea5d4072 100644 --- a/target-tomoko/presentation/presentation.hpp +++ b/target-tomoko/presentation/presentation.hpp @@ -21,11 +21,13 @@ struct Presentation : Window { MenuRadioItem videoScaleSmall{&videoScaleMenu}; MenuRadioItem videoScaleNormal{&videoScaleMenu}; MenuRadioItem videoScaleLarge{&videoScaleMenu}; + Group videoScales{&videoScaleSmall, &videoScaleNormal, &videoScaleLarge}; MenuSeparator videoScaleSeparator{&videoScaleMenu}; MenuCheckItem aspectCorrection{&videoScaleMenu}; Menu videoFilterMenu{&settingsMenu}; MenuRadioItem videoFilterNone{&videoFilterMenu}; MenuRadioItem videoFilterBlur{&videoFilterMenu}; + Group videoFilters{&videoFilterNone, &videoFilterBlur}; MenuSeparator videoFilterSeparator{&videoFilterMenu}; MenuCheckItem colorEmulation{&videoFilterMenu}; MenuCheckItem maskOverscan{&videoFilterMenu}; diff --git a/target-tomoko/settings/hotkeys.cpp b/target-tomoko/settings/hotkeys.cpp index a025e870..aee342a9 100644 --- a/target-tomoko/settings/hotkeys.cpp +++ b/target-tomoko/settings/hotkeys.cpp @@ -28,10 +28,14 @@ HotkeySettings::HotkeySettings(TabFrame* parent) : TabFrameItem(parent) { auto HotkeySettings::reloadMappings() -> void { mappingList.reset(); mappingList.append(ListViewColumn().setText("Name")); - mappingList.append(ListViewColumn().setText("Mapping").setWidth(~0)); + mappingList.append(ListViewColumn().setText("Mapping").setExpandable()); mappingList.append(ListViewColumn().setText("Device")); for(auto& hotkey : inputManager->hotkeys) { - mappingList.append(ListViewItem().setText(0, hotkey->name)); + mappingList.append(ListViewItem() + .append(ListViewCell().setText(hotkey->name)) + .append(ListViewCell()) + .append(ListViewCell()) + ); } mappingList.resizeColumns(); } @@ -39,7 +43,9 @@ auto HotkeySettings::reloadMappings() -> void { auto HotkeySettings::refreshMappings() -> void { unsigned position = 0; for(auto& hotkey : inputManager->hotkeys) { - mappingList.item(position++)->setText(1, hotkey->assignmentName()).setText(2, hotkey->deviceName()); + mappingList.item(position)->cell(1)->setText(hotkey->assignmentName()); + mappingList.item(position)->cell(2)->setText(hotkey->deviceName()); + position++; } mappingList.resizeColumns(); } diff --git a/target-tomoko/settings/input.cpp b/target-tomoko/settings/input.cpp index 3708d22f..99aec66f 100644 --- a/target-tomoko/settings/input.cpp +++ b/target-tomoko/settings/input.cpp @@ -83,10 +83,14 @@ auto InputSettings::reloadMappings() -> void { eraseButton.setEnabled(false); mappingList.reset(); mappingList.append(ListViewColumn().setText("Name")); - mappingList.append(ListViewColumn().setText("Mapping").setWidth(~0)); + mappingList.append(ListViewColumn().setText("Mapping").setExpandable()); mappingList.append(ListViewColumn().setText("Device").setForegroundColor({0, 128, 0})); for(auto& mapping : activeDevice().mappings) { - mappingList.append(ListViewItem().setText(0, mapping->name)); + mappingList.append(ListViewItem() + .append(ListViewCell().setText(mapping->name)) + .append(ListViewCell()) + .append(ListViewCell()) + ); } refreshMappings(); } @@ -94,7 +98,9 @@ auto InputSettings::reloadMappings() -> void { auto InputSettings::refreshMappings() -> void { unsigned position = 0; for(auto& mapping : activeDevice().mappings) { - mappingList.item(position++)->setText(1, mapping->assignmentName()).setText(2, mapping->deviceName()); + mappingList.item(position)->cell(1)->setText(mapping->assignmentName()); + mappingList.item(position)->cell(2)->setText(mapping->deviceName()); + position++; } mappingList.resizeColumns(); } diff --git a/target-tomoko/tools/cheat-database.cpp b/target-tomoko/tools/cheat-database.cpp index 3c547616..553cb644 100644 --- a/target-tomoko/tools/cheat-database.cpp +++ b/target-tomoko/tools/cheat-database.cpp @@ -3,12 +3,14 @@ CheatDatabase::CheatDatabase() { layout.setMargin(5); cheatList.setCheckable(); - selectAllButton.setText("Select All").onActivate([&] { cheatList.setChecked(true); }); - unselectAllButton.setText("Unselect All").onActivate([&] { cheatList.setChecked(false); }); + selectAllButton.setText("Select All").onActivate([&] { cheatList.checkAll(); }); + unselectAllButton.setText("Unselect All").onActivate([&] { cheatList.uncheckAll(); }); addCodesButton.setText("Add Codes").onActivate([&] { addCodes(); }); setSize({800, 400}); setPlacement(0.5, 1.0); + + onSize([&] { cheatList.resizeColumns(); }); } auto CheatDatabase::findCodes() -> void { @@ -23,14 +25,17 @@ auto CheatDatabase::findCodes() -> void { codes.reset(); cheatList.reset(); - cheatList.append(ListViewColumn().setWidth(~0)); + cheatList.append(ListViewColumn().setExpandable()); for(auto cheat : cartridge.find("cheat")) { codes.append(cheat["code"].text()); - cheatList.append(ListViewItem().setText(0, cheat["description"].text())); + cheatList.append(ListViewItem() + .append(ListViewCell().setText(cheat["description"].text())) + ); } setTitle(cartridge["name"].text()); setVisible(); + cheatList.resizeColumns(); return; } @@ -40,7 +45,7 @@ auto CheatDatabase::findCodes() -> void { auto CheatDatabase::addCodes() -> void { for(auto item : cheatList.checked()) { string code = codes(item->offset(), ""); - string description = item->text(0); + string description = item->cell(0)->text(); if(toolsManager->cheatEditor.addCode(code, description) == false) { MessageDialog().setParent(*this).setText("Free slots exhausted. Not all codes could be added.").warning(); break; diff --git a/target-tomoko/tools/cheat-editor.cpp b/target-tomoko/tools/cheat-editor.cpp index d249037e..eedb5112 100644 --- a/target-tomoko/tools/cheat-editor.cpp +++ b/target-tomoko/tools/cheat-editor.cpp @@ -5,8 +5,14 @@ CheatEditor::CheatEditor(TabFrame* parent) : TabFrameItem(parent) { layout.setMargin(5); cheatList.append(ListViewColumn().setText("Slot").setForegroundColor({0, 128, 0}).setHorizontalAlignment(1.0)); cheatList.append(ListViewColumn().setText("Code(s)")); - cheatList.append(ListViewColumn().setText("Description").setWidth(~0)); - for(auto slot : range(Slots)) cheatList.append(ListViewItem().setText(0, 1 + slot)); + cheatList.append(ListViewColumn().setText("Description").setExpandable()); + for(auto slot : range(Slots)) { + cheatList.append(ListViewItem() + .append(ListViewCell().setText(1 + slot)) + .append(ListViewCell()) + .append(ListViewCell()) + ); + } cheatList.setCheckable(); cheatList.setHeaderVisible(); cheatList.onChange([&] { doChangeSelected(); }); @@ -52,9 +58,13 @@ auto CheatEditor::doRefresh() -> void { if(cheat.code || cheat.description) { lstring codes = cheat.code.split("+"); if(codes.size() > 1) codes[0].append("+..."); - cheatList.item(slot)->setChecked(cheat.enabled).setText(1, codes[0]).setText(2, cheat.description); + cheatList.item(slot)->setChecked(cheat.enabled); + cheatList.item(slot)->cell(1)->setText(codes[0]); + cheatList.item(slot)->cell(2)->setText(cheat.description); } else { - cheatList.item(slot)->setChecked(false).setText(1, "").setText(2, "(empty)"); + cheatList.item(slot)->setChecked(false); + cheatList.item(slot)->cell(1)->setText(""); + cheatList.item(slot)->cell(2)->setText("(empty)"); } } @@ -68,7 +78,7 @@ auto CheatEditor::doReset(bool force) -> void { cheat.code = ""; cheat.description = ""; } - cheatList.setSelected(false); + cheatList.unselectAll(); doChangeSelected(); doRefresh(); synchronizeCodes(); diff --git a/target-tomoko/tools/state-manager.cpp b/target-tomoko/tools/state-manager.cpp index 1d093de1..7797a132 100644 --- a/target-tomoko/tools/state-manager.cpp +++ b/target-tomoko/tools/state-manager.cpp @@ -4,9 +4,12 @@ StateManager::StateManager(TabFrame* parent) : TabFrameItem(parent) { layout.setMargin(5); stateList.append(ListViewColumn().setText("Slot").setForegroundColor({0, 128, 0}).setHorizontalAlignment(1.0)); - stateList.append(ListViewColumn().setText("Description").setWidth(~0)); - for(unsigned slot = 0; slot < Slots; slot++) { - stateList.append(ListViewItem().setText(0, 1 + slot)); + stateList.append(ListViewColumn().setText("Description").setExpandable()); + for(auto slot : range(Slots)) { + stateList.append(ListViewItem() + .append(ListViewCell().setText(1 + slot)) + .append(ListViewCell()) + ); } stateList.setHeaderVisible(); stateList.onActivate([&] { doLoad(); }); @@ -48,9 +51,9 @@ auto StateManager::doRefresh() -> void { description.reserve(512); memory::copy(description.pointer(), buffer.data() + 72, 512); description.resize(description.length()); - stateList.item(slot)->setText(1, description); + stateList.item(slot)->cell(1)->setText(description); } else { - stateList.item(slot)->setText(1, "(empty)"); + stateList.item(slot)->cell(1)->setText("(empty)"); } } doChange();