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();