diff --git a/SurrealEngine/UI/Editor/EditorMainWindow.cpp b/SurrealEngine/UI/Editor/EditorMainWindow.cpp index 0608fdfa..2629dc36 100644 --- a/SurrealEngine/UI/Editor/EditorMainWindow.cpp +++ b/SurrealEngine/UI/Editor/EditorMainWindow.cpp @@ -6,9 +6,17 @@ #include "Package/PackageManager.h" #include "UObject/UActor.h" #include "UObject/ULevel.h" +#include EditorMainWindow::EditorMainWindow() { + GetMenubar()->AddItem("File", [this](Menu* menu) { OnFileMenu(menu); }); + GetMenubar()->AddItem("Edit", [this](Menu* menu) { OnEditMenu(menu); }); + GetMenubar()->AddItem("View", [this](Menu* menu) { OnViewMenu(menu); }); + GetMenubar()->AddItem("Tools", [this](Menu* menu) { OnToolsMenu(menu); }); + //GetMenubar()->AddItem("Window", [this](Menu* menu) { OnWindowMenu(menu); }); + GetMenubar()->AddItem("Help", [this](Menu* menu) { OnHelpMenu(menu); }); + Workspace = new EditorWorkspace(this); SetCentralWidget(Workspace); @@ -23,3 +31,138 @@ EditorMainWindow::EditorMainWindow() EditorMainWindow::~EditorMainWindow() { } + +void EditorMainWindow::OnFileMenu(Menu* menu) +{ + menu->AddItem({}, "New", [this]() { OnFileNew(); }); + menu->AddItem({}, "Open", [this]() { OnFileOpen(); }); + menu->AddSeparator(); + menu->AddItem({}, "Save", [this]() { OnFileSave(); }); + menu->AddSeparator(); + menu->AddItem({}, "Exit", [this]() { OnFileExit(); }); +} + +void EditorMainWindow::OnEditMenu(Menu* menu) +{ + menu->AddItem({}, "Undo", [this]() { OnEditUndo(); }); + menu->AddItem({}, "Redo", [this]() { OnEditRedo(); }); + menu->AddSeparator(); + menu->AddItem({}, "Cut", [this]() { OnEditCut(); }); + menu->AddItem({}, "Copy", [this]() { OnEditCopy(); }); + menu->AddItem({}, "Paste", [this]() { OnEditPaste(); }); + menu->AddItem({}, "Delete", [this]() { OnEditDelete(); }); + menu->AddSeparator(); + menu->AddItem({}, "Select all", [this]() { OnEditSelectAll(); }); +} + +void EditorMainWindow::OnViewMenu(Menu* menu) +{ + menu->AddItem({}, "Packages", [this]() { OnViewPackages(); }); + menu->AddItem({}, "Textures", [this]() { OnViewTextures(); }); + menu->AddItem({}, "Meshes", [this]() { OnViewMeshes(); }); + menu->AddItem({}, "Brushes", [this]() { OnViewBrushes(); }); + menu->AddItem({}, "Actors", [this]() { OnViewActors(); }); +} + +void EditorMainWindow::OnToolsMenu(Menu* menu) +{ + menu->AddItem({}, "Theme", [this]() { OnToolsTheme(); }); + menu->AddItem({}, "Customize", [this]() { OnToolsCustomize(); }); + menu->AddItem({}, "Options", [this]() { OnToolsOptions(); }); +} + +/* +void EditorMainWindow::OnWindowMenu(Menu* menu) +{ +} +*/ + +void EditorMainWindow::OnHelpMenu(Menu* menu) +{ + menu->AddItem({}, "View Help", [this]() { OnHelpHome(); }); + menu->AddItem({}, "About Surreal Engine", [this]() { OnHelpAbout(); }); +} + +void EditorMainWindow::OnFileNew() +{ +} + +void EditorMainWindow::OnFileOpen() +{ +} + +void EditorMainWindow::OnFileSave() +{ +} + +void EditorMainWindow::OnFileExit() +{ +} + +void EditorMainWindow::OnEditUndo() +{ +} + +void EditorMainWindow::OnEditRedo() +{ +} + +void EditorMainWindow::OnEditCut() +{ +} + +void EditorMainWindow::OnEditCopy() +{ +} + +void EditorMainWindow::OnEditPaste() +{ +} + +void EditorMainWindow::OnEditDelete() +{ +} + +void EditorMainWindow::OnEditSelectAll() +{ +} + +void EditorMainWindow::OnViewPackages() +{ +} + +void EditorMainWindow::OnViewTextures() +{ +} + +void EditorMainWindow::OnViewMeshes() +{ +} + +void EditorMainWindow::OnViewBrushes() +{ +} + +void EditorMainWindow::OnViewActors() +{ +} + +void EditorMainWindow::OnToolsTheme() +{ +} + +void EditorMainWindow::OnToolsCustomize() +{ +} + +void EditorMainWindow::OnToolsOptions() +{ +} + +void EditorMainWindow::OnHelpHome() +{ +} + +void EditorMainWindow::OnHelpAbout() +{ +} diff --git a/SurrealEngine/UI/Editor/EditorMainWindow.h b/SurrealEngine/UI/Editor/EditorMainWindow.h index d95d5e9c..691bffde 100644 --- a/SurrealEngine/UI/Editor/EditorMainWindow.h +++ b/SurrealEngine/UI/Editor/EditorMainWindow.h @@ -4,6 +4,7 @@ #include class EditorWorkspace; +class Menu; class EditorMainWindow : public MainWindow { @@ -14,4 +15,37 @@ class EditorMainWindow : public MainWindow EditorWorkspace* Workspace = nullptr; void OnClose() override { DisplayWindow::ExitLoop(); } + + void OnFileMenu(Menu* menu); + void OnEditMenu(Menu* menu); + void OnViewMenu(Menu* menu); + void OnToolsMenu(Menu* menu); + //void OnWindowMenu(Menu* menu); + void OnHelpMenu(Menu* menu); + + void OnFileNew(); + void OnFileOpen(); + void OnFileSave(); + void OnFileExit(); + + void OnEditUndo(); + void OnEditRedo(); + void OnEditCut(); + void OnEditCopy(); + void OnEditPaste(); + void OnEditDelete(); + void OnEditSelectAll(); + + void OnViewPackages(); + void OnViewTextures(); + void OnViewMeshes(); + void OnViewBrushes(); + void OnViewActors(); + + void OnToolsTheme(); + void OnToolsCustomize(); + void OnToolsOptions(); + + void OnHelpHome(); + void OnHelpAbout(); }; diff --git a/Thirdparty/ZWidget/CMakeLists.txt b/Thirdparty/ZWidget/CMakeLists.txt index ae50bf97..ccc5fcb1 100644 --- a/Thirdparty/ZWidget/CMakeLists.txt +++ b/Thirdparty/ZWidget/CMakeLists.txt @@ -33,6 +33,9 @@ set(ZWIDGET_SOURCES src/widgets/listview/listview.cpp src/widgets/tabwidget/tabwidget.cpp src/window/window.cpp + src/systemdialogs/folder_browse_dialog.cpp + src/systemdialogs/open_file_dialog.cpp + src/systemdialogs/save_file_dialog.cpp ) set(ZWIDGET_INCLUDES @@ -63,6 +66,9 @@ set(ZWIDGET_INCLUDES include/zwidget/widgets/listview/listview.h include/zwidget/widgets/tabwidget/tabwidget.h include/zwidget/window/window.h + include/zwidget/systemdialogs/folder_browse_dialog.h + include/zwidget/systemdialogs/open_file_dialog.h + include/zwidget/systemdialogs/save_file_dialog.h ) set(ZWIDGET_WIN32_SOURCES @@ -97,6 +103,7 @@ source_group("src\\widgets\\checkboxlabel" REGULAR_EXPRESSION "${CMAKE_CURRENT_S source_group("src\\widgets\\listview" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/listview/.+") source_group("src\\widgets\\tabwidget" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/tabwidget/.+") source_group("src\\window" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/.+") +source_group("src\\systemdialogs" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/systemdialogs/.+") source_group("include" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/.+") source_group("include\\core" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/core/.+") source_group("include\\widgets" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/.+") @@ -116,6 +123,7 @@ source_group("include\\widgets\\tabwidget" REGULAR_EXPRESSION "${CMAKE_CURRENT_S source_group("include\\window" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/.+") source_group("include\\window\\win32" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/win32/.+") source_group("include\\window\\sdl2" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/sdl2/.+") +source_group("include\\systemdialogs" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/systemdialogs/.+") include_directories(include include/zwidget src) @@ -136,7 +144,6 @@ endif() if(MSVC) # Use all cores for compilation - #set(CMAKE_CXX_FLAGS "/MP ${CMAKE_CXX_FLAGS}") set(CMAKE_CXX_FLAGS "/MP ${CMAKE_CXX_FLAGS} /D_ITERATOR_DEBUG_LEVEL=0") # Ignore warnings in third party code diff --git a/Thirdparty/ZWidget/include/zwidget/core/widget.h b/Thirdparty/ZWidget/include/zwidget/core/widget.h index 02e87729..43f9094b 100644 --- a/Thirdparty/ZWidget/include/zwidget/core/widget.h +++ b/Thirdparty/ZWidget/include/zwidget/core/widget.h @@ -129,6 +129,8 @@ class Widget : DisplayWindowHost static Size GetScreenSize(); + void* GetNativeHandle(); + protected: virtual void OnPaintFrame(Canvas* canvas); virtual void OnPaint(Canvas* canvas) { } diff --git a/Thirdparty/ZWidget/include/zwidget/systemdialogs/folder_browse_dialog.h b/Thirdparty/ZWidget/include/zwidget/systemdialogs/folder_browse_dialog.h new file mode 100644 index 00000000..991869a5 --- /dev/null +++ b/Thirdparty/ZWidget/include/zwidget/systemdialogs/folder_browse_dialog.h @@ -0,0 +1,30 @@ + +#pragma once + +#include +#include + +class Widget; + +/// \brief Displays the system folder browsing dialog +class BrowseFolderDialog +{ +public: + /// \brief Constructs an browse folder dialog. + static std::unique_ptr Create(Widget*owner); + + virtual ~BrowseFolderDialog() = default; + + /// \brief Get the full path of the directory selected. + virtual std::string SelectedPath() const = 0; + + /// \brief Sets the initial directory that is displayed. + virtual void SetInitialDirectory(const std::string& path) = 0; + + /// \brief Sets the text that appears in the title bar. + virtual void SetTitle(const std::string& title) = 0; + + /// \brief Shows the file dialog. + /// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise. + virtual bool Show() = 0; +}; diff --git a/Thirdparty/ZWidget/include/zwidget/systemdialogs/open_file_dialog.h b/Thirdparty/ZWidget/include/zwidget/systemdialogs/open_file_dialog.h new file mode 100644 index 00000000..29ede966 --- /dev/null +++ b/Thirdparty/ZWidget/include/zwidget/systemdialogs/open_file_dialog.h @@ -0,0 +1,55 @@ + +#pragma once + +#include +#include +#include + +class Widget; + +/// \brief Displays the system open file dialog. +class OpenFileDialog +{ +public: + /// \brief Constructs an open file dialog. + static std::unique_ptr Create(Widget* owner); + + virtual ~OpenFileDialog() = default; + + /// \brief Get the full path of the file selected. + /// + /// If multiple files are selected, this returns the first file. + virtual std::string Filename() const = 0; + + /// \brief Gets an array that contains one file name for each selected file. + virtual std::vector Filenames() const = 0; + + /// \brief Sets if multiple files can be selected or not. + /// \param multiselect = When true, multiple items can be selected. + virtual void SetMultiSelect(bool multiselect) = 0; + + /// \brief Sets a string containing the full path of the file selected. + virtual void SetFilename(const std::string &filename) = 0; + + /// \brief Sets the default extension to use. + virtual void SetDefaultExtension(const std::string& extension) = 0; + + /// \brief Add a filter that determines what types of files are displayed. + virtual void AddFilter(const std::string &filter_description, const std::string &filter_extension) = 0; + + /// \brief Clears all filters. + virtual void ClearFilters() = 0; + + /// \brief Sets a default filter, on a 0-based index. + virtual void SetFilterIndex(int filter_index) = 0; + + /// \brief Sets the initial directory that is displayed. + virtual void SetInitialDirectory(const std::string &path) = 0; + + /// \brief Sets the text that appears in the title bar. + virtual void SetTitle(const std::string &title) = 0; + + /// \brief Shows the file dialog. + /// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise. + virtual bool Show() = 0; +}; diff --git a/Thirdparty/ZWidget/include/zwidget/systemdialogs/save_file_dialog.h b/Thirdparty/ZWidget/include/zwidget/systemdialogs/save_file_dialog.h new file mode 100644 index 00000000..ce10a127 --- /dev/null +++ b/Thirdparty/ZWidget/include/zwidget/systemdialogs/save_file_dialog.h @@ -0,0 +1,46 @@ + +#pragma once + +#include +#include +#include + +class Widget; + +/// \brief Displays the system save file dialog. +class SaveFileDialog +{ +public: + /// \brief Constructs a save file dialog. + static std::unique_ptr Create(Widget *owner); + + virtual ~SaveFileDialog() = default; + + /// \brief Get the full path of the file selected. + virtual std::string Filename() const = 0; + + /// \brief Sets a string containing the full path of the file selected. + virtual void SetFilename(const std::string &filename) = 0; + + /// \brief Sets the default extension to use. + virtual void SetDefaultExtension(const std::string& extension) = 0; + + /// \brief Add a filter that determines what types of files are displayed. + virtual void AddFilter(const std::string &filter_description, const std::string &filter_extension) = 0; + + /// \brief Clears all filters. + virtual void ClearFilters() = 0; + + /// \brief Sets a default filter, on a 0-based index. + virtual void SetFilterIndex(int filter_index) = 0; + + /// \brief Sets the initial directory that is displayed. + virtual void SetInitialDirectory(const std::string &path) = 0; + + /// \brief Sets the text that appears in the title bar. + virtual void SetTitle(const std::string &title) = 0; + + /// \brief Shows the file dialog. + /// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise. + virtual bool Show() = 0; +}; diff --git a/Thirdparty/ZWidget/include/zwidget/widgets/imagebox/imagebox.h b/Thirdparty/ZWidget/include/zwidget/widgets/imagebox/imagebox.h index e3a72438..7d3aa520 100644 --- a/Thirdparty/ZWidget/include/zwidget/widgets/imagebox/imagebox.h +++ b/Thirdparty/ZWidget/include/zwidget/widgets/imagebox/imagebox.h @@ -11,6 +11,7 @@ class ImageBox : public Widget void SetImage(std::shared_ptr newImage); + double GetPreferredWidth() const; double GetPreferredHeight() const; protected: diff --git a/Thirdparty/ZWidget/include/zwidget/widgets/menubar/menubar.h b/Thirdparty/ZWidget/include/zwidget/widgets/menubar/menubar.h index 9c13283c..d9f08439 100644 --- a/Thirdparty/ZWidget/include/zwidget/widgets/menubar/menubar.h +++ b/Thirdparty/ZWidget/include/zwidget/widgets/menubar/menubar.h @@ -2,6 +2,13 @@ #pragma once #include "../../core/widget.h" +#include "../textlabel/textlabel.h" +#include "../imagebox/imagebox.h" + +class Menu; +class MenubarItem; +class MenuItem; +class MenuItemSeparator; class Menubar : public Widget { @@ -9,6 +16,83 @@ class Menubar : public Widget Menubar(Widget* parent); ~Menubar(); + MenubarItem* AddItem(std::string text, std::function onOpen, bool alignRight = false); + +protected: + void OnGeometryChanged() override; + +private: + void ShowMenu(MenubarItem* item); + void CloseMenu(); + + std::vector menuItems; + Menu* openMenu = nullptr; + + friend class MenubarItem; +}; + +class MenubarItem : public Widget +{ +public: + MenubarItem(Menubar* menubar, std::string text, bool alignRight); + + void SetOpenCallback(std::function callback) { onOpen = std::move(callback); } + const std::function& GetOpenCallback() const { return onOpen; } + + double GetPreferredWidth() const; + + bool AlignRight = false; + protected: void OnPaint(Canvas* canvas) override; + bool OnMouseDown(const Point& pos, InputKey key) override; + bool OnMouseUp(const Point& pos, InputKey key) override; + void OnMouseMove(const Point& pos) override; + void OnMouseLeave() override; + +private: + Menubar* menubar = nullptr; + std::function onOpen; + std::string text; +}; + +class Menu : public Widget +{ +public: + Menu(Widget* parent); + + void SetLeftPosition(const Point& globalPos); + void SetRightPosition(const Point& globalPos); + MenuItem* AddItem(std::shared_ptr icon, std::string text, std::function onClick = {}); + MenuItemSeparator* AddSeparator(); + + double GetPreferredWidth() const; + double GetPreferredHeight() const; + +protected: + void OnGeometryChanged() override; + + std::function onCloseMenu; + + friend class Menubar; +}; + +class MenuItem : public Widget +{ +public: + MenuItem(Widget* parent); + + ImageBox* icon = nullptr; + TextLabel* text = nullptr; + +protected: + void OnMouseMove(const Point& pos) override; + void OnMouseLeave() override; + void OnGeometryChanged() override; +}; + +class MenuItemSeparator : public Widget +{ +public: + MenuItemSeparator(Widget* parent); }; diff --git a/Thirdparty/ZWidget/include/zwidget/window/window.h b/Thirdparty/ZWidget/include/zwidget/window/window.h index 2c9a413b..538c7041 100644 --- a/Thirdparty/ZWidget/include/zwidget/window/window.h +++ b/Thirdparty/ZWidget/include/zwidget/window/window.h @@ -118,7 +118,7 @@ class DisplayWindowHost class DisplayWindow { public: - static std::unique_ptr Create(DisplayWindowHost* windowHost); + static std::unique_ptr Create(DisplayWindowHost* windowHost, bool popupWindow); static void ProcessEvents(); static void RunLoop(); @@ -157,6 +157,9 @@ class DisplayWindow virtual int GetPixelHeight() const = 0; virtual double GetDpiScale() const = 0; + virtual Point MapFromGlobal(const Point& pos) const = 0; + virtual Point MapToGlobal(const Point& pos) const = 0; + virtual void SetBorderColor(uint32_t bgra8) = 0; virtual void SetCaptionColor(uint32_t bgra8) = 0; virtual void SetCaptionTextColor(uint32_t bgra8) = 0; @@ -165,4 +168,6 @@ class DisplayWindow virtual std::string GetClipboardText() = 0; virtual void SetClipboardText(const std::string& text) = 0; + + virtual void* GetNativeHandle() = 0; }; diff --git a/Thirdparty/ZWidget/src/core/theme.cpp b/Thirdparty/ZWidget/src/core/theme.cpp index f30bffdf..b85b8974 100644 --- a/Thirdparty/ZWidget/src/core/theme.cpp +++ b/Thirdparty/ZWidget/src/core/theme.cpp @@ -154,6 +154,9 @@ DarkWidgetTheme::DarkWidgetTheme() auto tabbar_tab = RegisterStyle(std::make_unique(widget), "tabbar-tab"); auto tabwidget_stack = RegisterStyle(std::make_unique(widget), "tabwidget-stack"); auto checkbox_label = RegisterStyle(std::make_unique(widget), "checkbox-label"); + auto menubaritem = RegisterStyle(std::make_unique(widget), "menubaritem"); + auto menu = RegisterStyle(std::make_unique(widget), "menu"); + auto menuitem = RegisterStyle(std::make_unique(widget), "menuitem"); widget->SetString("font-family", "NotoSans"); widget->SetColor("color", Colorf::fromRgba8(226, 223, 219)); @@ -229,6 +232,21 @@ DarkWidgetTheme::DarkWidgetTheme() checkbox_label->SetColor("checked-color", Colorf::fromRgba8(226, 223, 219)); checkbox_label->SetColor("unchecked-outer-border-color", Colorf::fromRgba8(99, 99, 99)); checkbox_label->SetColor("unchecked-inner-border-color", Colorf::fromRgba8(51, 51, 51)); + + menubaritem->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78)); + menubaritem->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88)); + + menu->SetDouble("noncontent-left", 5.0); + menu->SetDouble("noncontent-top", 5.0); + menu->SetDouble("noncontent-right", 5.0); + menu->SetDouble("noncontent-bottom", 5.0); + menu->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100)); + menu->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100)); + menu->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100)); + menu->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100)); + + menuitem->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78)); + menuitem->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88)); } ///////////////////////////////////////////////////////////////////////////// @@ -246,6 +264,9 @@ LightWidgetTheme::LightWidgetTheme() auto tabbar_tab = RegisterStyle(std::make_unique(widget), "tabbar-tab"); auto tabwidget_stack = RegisterStyle(std::make_unique(widget), "tabwidget-stack"); auto checkbox_label = RegisterStyle(std::make_unique(widget), "checkbox-label"); + auto menubaritem = RegisterStyle(std::make_unique(widget), "menubaritem"); + auto menu = RegisterStyle(std::make_unique(widget), "menu"); + auto menuitem = RegisterStyle(std::make_unique(widget), "menuitem"); widget->SetString("font-family", "NotoSans"); widget->SetColor("color", Colorf::fromRgba8(0, 0, 0)); @@ -321,4 +342,19 @@ LightWidgetTheme::LightWidgetTheme() checkbox_label->SetColor("checked-color", Colorf::fromRgba8(50, 50, 50)); checkbox_label->SetColor("unchecked-outer-border-color", Colorf::fromRgba8(156, 156, 156)); checkbox_label->SetColor("unchecked-inner-border-color", Colorf::fromRgba8(200, 200, 200)); + + menubaritem->SetColor("hover", "background-color", Colorf::fromRgba8(200, 200, 200)); + menubaritem->SetColor("down", "background-color", Colorf::fromRgba8(190, 190, 190)); + + menu->SetDouble("noncontent-left", 5.0); + menu->SetDouble("noncontent-top", 5.0); + menu->SetDouble("noncontent-right", 5.0); + menu->SetDouble("noncontent-bottom", 5.0); + menu->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155)); + menu->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155)); + menu->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155)); + menu->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155)); + + menuitem->SetColor("hover", "background-color", Colorf::fromRgba8(200, 200, 200)); + menuitem->SetColor("down", "background-color", Colorf::fromRgba8(190, 190, 190)); } diff --git a/Thirdparty/ZWidget/src/core/widget.cpp b/Thirdparty/ZWidget/src/core/widget.cpp index e27fff17..c1a8156b 100644 --- a/Thirdparty/ZWidget/src/core/widget.cpp +++ b/Thirdparty/ZWidget/src/core/widget.cpp @@ -9,7 +9,7 @@ Widget::Widget(Widget* parent, WidgetType type) : Type(type) { if (type != WidgetType::Child) { - DispWindow = DisplayWindow::Create(this); + DispWindow = DisplayWindow::Create(this, type == WidgetType::Popup); DispCanvas = Canvas::create(DispWindow.get()); SetStyleState("root"); @@ -510,7 +510,7 @@ Point Widget::MapFromGlobal(const Point& pos) const { if (cur->DispWindow) { - return p - cur->GetFrameGeometry().topLeft(); + return cur->DispWindow->MapFromGlobal(p); } p -= cur->ContentGeometry.topLeft(); } @@ -536,7 +536,7 @@ Point Widget::MapToGlobal(const Point& pos) const { if (cur->DispWindow) { - return cur->GetFrameGeometry().topLeft() + p; + return cur->DispWindow->MapToGlobal(p); } p += cur->ContentGeometry.topLeft(); } @@ -705,9 +705,21 @@ void Widget::OnWindowKeyUp(InputKey key) void Widget::OnWindowGeometryChanged() { + if (!DispWindow) + return; Size size = DispWindow->GetClientSize(); FrameGeometry = Rect::xywh(0.0, 0.0, size.width, size.height); - ContentGeometry = FrameGeometry; + + double left = FrameGeometry.left() + GetNoncontentLeft(); + double top = FrameGeometry.top() + GetNoncontentTop(); + double right = FrameGeometry.right() - GetNoncontentRight(); + double bottom = FrameGeometry.bottom() - GetNoncontentBottom(); + left = std::min(left, FrameGeometry.right()); + top = std::min(top, FrameGeometry.bottom()); + right = std::max(right, FrameGeometry.left()); + bottom = std::max(bottom, FrameGeometry.top()); + ContentGeometry = Rect::ltrb(left, top, right, bottom); + OnGeometryChanged(); } @@ -733,6 +745,12 @@ Size Widget::GetScreenSize() return DisplayWindow::GetScreenSize(); } +void* Widget::GetNativeHandle() +{ + Widget* w = Window(); + return w ? w->DispWindow->GetNativeHandle() : nullptr; +} + void Widget::SetStyleClass(const std::string& themeClass) { if (StyleClass != themeClass) diff --git a/Thirdparty/ZWidget/src/systemdialogs/folder_browse_dialog.cpp b/Thirdparty/ZWidget/src/systemdialogs/folder_browse_dialog.cpp new file mode 100644 index 00000000..754e397e --- /dev/null +++ b/Thirdparty/ZWidget/src/systemdialogs/folder_browse_dialog.cpp @@ -0,0 +1,188 @@ + +#include "systemdialogs/folder_browse_dialog.h" +#include "core/widget.h" +#include "window/window.h" +#include + +#if defined(WIN32) +#include + +namespace +{ + static std::string from_utf16(const std::wstring& str) + { + if (str.empty()) return {}; + int needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0, nullptr, nullptr); + if (needed == 0) + throw std::runtime_error("WideCharToMultiByte failed"); + std::string result; + result.resize(needed); + needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size(), nullptr, nullptr); + if (needed == 0) + throw std::runtime_error("WideCharToMultiByte failed"); + return result; + } + + static std::wstring to_utf16(const std::string& str) + { + if (str.empty()) return {}; + int needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0); + if (needed == 0) + throw std::runtime_error("MultiByteToWideChar failed"); + std::wstring result; + result.resize(needed); + needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size()); + if (needed == 0) + throw std::runtime_error("MultiByteToWideChar failed"); + return result; + } + + template + class ComPtr + { + public: + ComPtr() { Ptr = nullptr; } + ComPtr(const ComPtr& other) { Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } + ComPtr(ComPtr&& move) { Ptr = move.Ptr; move.Ptr = nullptr; } + ~ComPtr() { reset(); } + ComPtr& operator=(const ComPtr& other) { if (this != &other) { if (Ptr) Ptr->Release(); Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } return *this; } + void reset() { if (Ptr) Ptr->Release(); Ptr = nullptr; } + T* get() { return Ptr; } + static IID GetIID() { return __uuidof(T); } + void** InitPtr() { return (void**)TypedInitPtr(); } + T** TypedInitPtr() { reset(); return &Ptr; } + operator T* () const { return Ptr; } + T* operator ->() const { return Ptr; } + T* Ptr; + }; +} + +class BrowseFolderDialogImpl : public BrowseFolderDialog +{ +public: + BrowseFolderDialogImpl(Widget *owner) : owner(owner) + { + } + + Widget *owner = nullptr; + + std::string selected_path; + std::string initial_directory; + std::string title; + + bool Show() override + { + std::wstring title16 = to_utf16(title); + std::wstring initial_directory16 = to_utf16(initial_directory); + + HRESULT result; + ComPtr open_dialog; + + result = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_ALL, open_dialog.GetIID(), open_dialog.InitPtr()); + throw_if_failed(result, "CoCreateInstance(FileOpenDialog) failed"); + + result = open_dialog->SetTitle(title16.c_str()); + throw_if_failed(result, "IFileOpenDialog.SetTitle failed"); + + result = open_dialog->SetOptions(FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST); + throw_if_failed(result, "IFileOpenDialog.SetOptions((FOS_PICKFOLDERS) failed"); + + if (initial_directory16.length() > 0) + { + LPITEMIDLIST item_id_list = nullptr; + SFGAOF flags = 0; + result = SHParseDisplayName(initial_directory16.c_str(), nullptr, &item_id_list, SFGAO_FILESYSTEM, &flags); + throw_if_failed(result, "SHParseDisplayName failed"); + + ComPtr folder_item; + result = SHCreateShellItem(nullptr, nullptr, item_id_list, folder_item.TypedInitPtr()); + ILFree(item_id_list); + throw_if_failed(result, "SHCreateItemFromParsingName failed"); + + /* This code requires Windows Vista or newer: + ComPtr folder_item; + result = SHCreateItemFromParsingName(initial_directory16.c_str(), nullptr, folder_item.GetIID(), folder_item.InitPtr()); + throw_if_failed(result, "SHCreateItemFromParsingName failed"); + */ + + if (folder_item) + { + result = open_dialog->SetFolder(folder_item); + throw_if_failed(result, "IFileOpenDialog.SetFolder failed"); + } + } + + if (owner && owner->Window()) + result = open_dialog->Show((HWND)owner->Window()->GetNativeHandle()); + else + result = open_dialog->Show(0); + + if (SUCCEEDED(result)) + { + ComPtr chosen_folder; + result = open_dialog->GetResult(chosen_folder.TypedInitPtr()); + throw_if_failed(result, "IFileOpenDialog.GetResult failed"); + + WCHAR *buffer = nullptr; + result = chosen_folder->GetDisplayName(SIGDN_FILESYSPATH, &buffer); + throw_if_failed(result, "IShellItem.GetDisplayName failed"); + + std::wstring output_directory16; + if (buffer) + { + try + { + output_directory16 = buffer; + } + catch (...) + { + CoTaskMemFree(buffer); + throw; + } + } + + CoTaskMemFree(buffer); + selected_path = from_utf16(output_directory16); + return true; + } + else + { + return false; + } + } + + std::string BrowseFolderDialog::SelectedPath() const override + { + return selected_path; + } + + void BrowseFolderDialog::SetInitialDirectory(const std::string& path) override + { + initial_directory = path; + } + + void BrowseFolderDialog::SetTitle(const std::string& newtitle) override + { + title = newtitle; + } + + void throw_if_failed(HRESULT result, const std::string &error) + { + if (FAILED(result)) + throw std::runtime_error(error); + } +}; + +std::unique_ptr BrowseFolderDialog::Create(Widget* owner) +{ + return std::make_unique(owner); +} + +#else + +std::unique_ptr BrowseFolderDialog::Create(Widget* owner) +{ + return {}; +} + +#endif diff --git a/Thirdparty/ZWidget/src/systemdialogs/open_file_dialog.cpp b/Thirdparty/ZWidget/src/systemdialogs/open_file_dialog.cpp new file mode 100644 index 00000000..ca7bc8f3 --- /dev/null +++ b/Thirdparty/ZWidget/src/systemdialogs/open_file_dialog.cpp @@ -0,0 +1,286 @@ + +#include "systemdialogs/open_file_dialog.h" +#include "core/widget.h" +#include "window/window.h" +#include + +#if defined(WIN32) +#include + +namespace +{ + static std::string from_utf16(const std::wstring& str) + { + if (str.empty()) return {}; + int needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0, nullptr, nullptr); + if (needed == 0) + throw std::runtime_error("WideCharToMultiByte failed"); + std::string result; + result.resize(needed); + needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size(), nullptr, nullptr); + if (needed == 0) + throw std::runtime_error("WideCharToMultiByte failed"); + return result; + } + + static std::wstring to_utf16(const std::string& str) + { + if (str.empty()) return {}; + int needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0); + if (needed == 0) + throw std::runtime_error("MultiByteToWideChar failed"); + std::wstring result; + result.resize(needed); + needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size()); + if (needed == 0) + throw std::runtime_error("MultiByteToWideChar failed"); + return result; + } + + template + class ComPtr + { + public: + ComPtr() { Ptr = nullptr; } + ComPtr(const ComPtr& other) { Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } + ComPtr(ComPtr&& move) { Ptr = move.Ptr; move.Ptr = nullptr; } + ~ComPtr() { reset(); } + ComPtr& operator=(const ComPtr& other) { if (this != &other) { if (Ptr) Ptr->Release(); Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } return *this; } + void reset() { if (Ptr) Ptr->Release(); Ptr = nullptr; } + T* get() { return Ptr; } + static IID GetIID() { return __uuidof(T); } + void** InitPtr() { return (void**)TypedInitPtr(); } + T** TypedInitPtr() { reset(); return &Ptr; } + operator T* () const { return Ptr; } + T* operator ->() const { return Ptr; } + T* Ptr; + }; +} + +class OpenFileDialogImpl : public OpenFileDialog +{ +public: + OpenFileDialogImpl(Widget* owner) : owner(owner) + { + } + + Widget* owner = nullptr; + + std::string initial_directory; + std::string initial_filename; + std::string title; + std::vector filenames; + bool multi_select = false; + + struct Filter + { + std::string description; + std::string extension; + }; + std::vector filters; + int filterindex = 0; + std::string defaultext; + + bool Show() override + { + std::wstring title16 = to_utf16(title); + std::wstring initial_directory16 = to_utf16(initial_directory); + + HRESULT result; + ComPtr open_dialog; + + result = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_ALL, open_dialog.GetIID(), open_dialog.InitPtr()); + throw_if_failed(result, "CoCreateInstance(FileOpenDialog) failed"); + + result = open_dialog->SetTitle(title16.c_str()); + throw_if_failed(result, "IFileOpenDialog.SetTitle failed"); + + if (!initial_filename.empty()) + { + result = open_dialog->SetFileName(to_utf16(initial_filename).c_str()); + throw_if_failed(result, "IFileOpenDialog.SetFileName failed"); + } + + FILEOPENDIALOGOPTIONS options = FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST; + if (multi_select) + options |= FOS_ALLOWMULTISELECT; + result = open_dialog->SetOptions(options); + throw_if_failed(result, "IFileOpenDialog.SetOptions() failed"); + + if (!filters.empty()) + { + std::vector filterspecs(filters.size()); + std::vector descriptions(filters.size()); + std::vector extensions(filters.size()); + for (size_t i = 0; i < filters.size(); i++) + { + descriptions[i] = to_utf16(filters[i].description); + extensions[i] = to_utf16(filters[i].extension); + COMDLG_FILTERSPEC& spec = filterspecs[i]; + spec.pszName = descriptions[i].c_str(); + spec.pszSpec = extensions[i].c_str(); + } + result = open_dialog->SetFileTypes((UINT)filterspecs.size(), filterspecs.data()); + throw_if_failed(result, "IFileOpenDialog.SetFileTypes() failed"); + + if ((size_t)filterindex < filters.size()) + { + result = open_dialog->SetFileTypeIndex(filterindex); + throw_if_failed(result, "IFileOpenDialog.SetFileTypeIndex() failed"); + } + } + + if (!defaultext.empty()) + { + result = open_dialog->SetDefaultExtension(to_utf16(defaultext).c_str()); + throw_if_failed(result, "IFileOpenDialog.SetDefaultExtension() failed"); + } + + if (initial_directory16.length() > 0) + { + LPITEMIDLIST item_id_list = nullptr; + SFGAOF flags = 0; + result = SHParseDisplayName(initial_directory16.c_str(), nullptr, &item_id_list, SFGAO_FILESYSTEM, &flags); + throw_if_failed(result, "SHParseDisplayName failed"); + + ComPtr folder_item; + result = SHCreateShellItem(nullptr, nullptr, item_id_list, folder_item.TypedInitPtr()); + ILFree(item_id_list); + throw_if_failed(result, "SHCreateItemFromParsingName failed"); + + /* This code requires Windows Vista or newer: + ComPtr folder_item; + result = SHCreateItemFromParsingName(initial_directory16.c_str(), nullptr, folder_item.GetIID(), folder_item.InitPtr()); + throw_if_failed(result, "SHCreateItemFromParsingName failed"); + */ + + if (folder_item) + { + result = open_dialog->SetFolder(folder_item); + throw_if_failed(result, "IFileOpenDialog.SetFolder failed"); + } + } + + if (owner && owner->Window()) + result = open_dialog->Show((HWND)owner->Window()->GetNativeHandle()); + else + result = open_dialog->Show(0); + + if (SUCCEEDED(result)) + { + ComPtr items; + result = open_dialog->GetSelectedItems(items.TypedInitPtr()); + throw_if_failed(result, "IFileOpenDialog.GetSelectedItems failed"); + + DWORD num_items = 0; + result = items->GetCount(&num_items); + throw_if_failed(result, "IShellItemArray.GetCount failed"); + + for (DWORD i = 0; i < num_items; i++) + { + ComPtr item; + result = items->GetItemAt(i, item.TypedInitPtr()); + throw_if_failed(result, "IShellItemArray.GetItemAt failed"); + + WCHAR* buffer = nullptr; + result = item->GetDisplayName(SIGDN_FILESYSPATH, &buffer); + throw_if_failed(result, "IShellItem.GetDisplayName failed"); + + std::wstring output16; + if (buffer) + { + try + { + output16 = buffer; + } + catch (...) + { + CoTaskMemFree(buffer); + throw; + } + } + + CoTaskMemFree(buffer); + filenames.push_back(from_utf16(output16)); + } + return true; + } + else + { + return false; + } + } + + std::string Filename() const override + { + return !filenames.empty() ? filenames.front() : std::string(); + } + + std::vector Filenames() const override + { + return filenames; + } + + void SetMultiSelect(bool new_multi_select) override + { + multi_select = new_multi_select; + } + + void SetFilename(const std::string& filename) override + { + initial_filename = filename; + } + + void AddFilter(const std::string& filter_description, const std::string& filter_extension) override + { + Filter f; + f.description = filter_description; + f.extension = filter_extension; + filters.push_back(std::move(f)); + } + + void ClearFilters() override + { + filters.clear(); + } + + void SetFilterIndex(int filter_index) override + { + filterindex = filter_index; + } + + void SetInitialDirectory(const std::string& path) override + { + initial_directory = path; + } + + void SetTitle(const std::string& newtitle) override + { + title = newtitle; + } + + void SetDefaultExtension(const std::string& extension) override + { + defaultext = extension; + } + + void throw_if_failed(HRESULT result, const std::string& error) + { + if (FAILED(result)) + throw std::runtime_error(error); + } +}; + +std::unique_ptr OpenFileDialog::Create(Widget* owner) +{ + return std::make_unique(owner); +} + +#else + +std::unique_ptr OpenFileDialog::Create(Widget* owner) +{ + return {}; +} + +#endif diff --git a/Thirdparty/ZWidget/src/systemdialogs/save_file_dialog.cpp b/Thirdparty/ZWidget/src/systemdialogs/save_file_dialog.cpp new file mode 100644 index 00000000..7bd8c00c --- /dev/null +++ b/Thirdparty/ZWidget/src/systemdialogs/save_file_dialog.cpp @@ -0,0 +1,263 @@ + +#include "systemdialogs/save_file_dialog.h" +#include "core/widget.h" +#include "window/window.h" +#include + +#if defined(WIN32) +#include + +namespace +{ + static std::string from_utf16(const std::wstring& str) + { + if (str.empty()) return {}; + int needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0, nullptr, nullptr); + if (needed == 0) + throw std::runtime_error("WideCharToMultiByte failed"); + std::string result; + result.resize(needed); + needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size(), nullptr, nullptr); + if (needed == 0) + throw std::runtime_error("WideCharToMultiByte failed"); + return result; + } + + static std::wstring to_utf16(const std::string& str) + { + if (str.empty()) return {}; + int needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0); + if (needed == 0) + throw std::runtime_error("MultiByteToWideChar failed"); + std::wstring result; + result.resize(needed); + needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size()); + if (needed == 0) + throw std::runtime_error("MultiByteToWideChar failed"); + return result; + } + + template + class ComPtr + { + public: + ComPtr() { Ptr = nullptr; } + ComPtr(const ComPtr& other) { Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } + ComPtr(ComPtr&& move) { Ptr = move.Ptr; move.Ptr = nullptr; } + ~ComPtr() { reset(); } + ComPtr& operator=(const ComPtr& other) { if (this != &other) { if (Ptr) Ptr->Release(); Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } return *this; } + void reset() { if (Ptr) Ptr->Release(); Ptr = nullptr; } + T* get() { return Ptr; } + static IID GetIID() { return __uuidof(T); } + void** InitPtr() { return (void**)TypedInitPtr(); } + T** TypedInitPtr() { reset(); return &Ptr; } + operator T* () const { return Ptr; } + T* operator ->() const { return Ptr; } + T* Ptr; + }; +} + +class SaveFileDialogImpl : public SaveFileDialog +{ +public: + SaveFileDialogImpl(Widget* owner) : owner(owner) + { + } + + Widget* owner = nullptr; + + std::string filename; + std::string initial_directory; + std::string initial_filename; + std::string title; + std::vector filenames; + + struct Filter + { + std::string description; + std::string extension; + }; + std::vector filters; + int filterindex = 0; + std::string defaultext; + + bool Show() override + { + std::wstring title16 = to_utf16(title); + std::wstring initial_directory16 = to_utf16(initial_directory); + + HRESULT result; + ComPtr save_dialog; + + result = CoCreateInstance(CLSID_FileSaveDialog, nullptr, CLSCTX_ALL, save_dialog.GetIID(), save_dialog.InitPtr()); + throw_if_failed(result, "CoCreateInstance(FileSaveDialog) failed"); + + result = save_dialog->SetTitle(title16.c_str()); + throw_if_failed(result, "IFileSaveDialog.SetTitle failed"); + + if (!initial_filename.empty()) + { + result = save_dialog->SetFileName(to_utf16(initial_filename).c_str()); + throw_if_failed(result, "IFileSaveDialog.SetFileName failed"); + } + + FILEOPENDIALOGOPTIONS options = FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST; + result = save_dialog->SetOptions(options); + throw_if_failed(result, "IFileSaveDialog.SetOptions() failed"); + + if (!filters.empty()) + { + std::vector filterspecs(filters.size()); + std::vector descriptions(filters.size()); + std::vector extensions(filters.size()); + for (size_t i = 0; i < filters.size(); i++) + { + descriptions[i] = to_utf16(filters[i].description); + extensions[i] = to_utf16(filters[i].extension); + COMDLG_FILTERSPEC& spec = filterspecs[i]; + spec.pszName = descriptions[i].c_str(); + spec.pszSpec = extensions[i].c_str(); + } + result = save_dialog->SetFileTypes((UINT)filterspecs.size(), filterspecs.data()); + throw_if_failed(result, "IFileOpenDialog.SetFileTypes() failed"); + + if ((size_t)filterindex < filters.size()) + { + result = save_dialog->SetFileTypeIndex(filterindex); + throw_if_failed(result, "IFileOpenDialog.SetFileTypeIndex() failed"); + } + } + + if (!defaultext.empty()) + { + result = save_dialog->SetDefaultExtension(to_utf16(defaultext).c_str()); + throw_if_failed(result, "IFileOpenDialog.SetDefaultExtension() failed"); + } + + if (initial_directory16.length() > 0) + { + LPITEMIDLIST item_id_list = nullptr; + SFGAOF flags = 0; + result = SHParseDisplayName(initial_directory16.c_str(), nullptr, &item_id_list, SFGAO_FILESYSTEM, &flags); + throw_if_failed(result, "SHParseDisplayName failed"); + + ComPtr folder_item; + result = SHCreateShellItem(nullptr, nullptr, item_id_list, folder_item.TypedInitPtr()); + ILFree(item_id_list); + throw_if_failed(result, "SHCreateItemFromParsingName failed"); + + /* This code requires Windows Vista or newer: + ComPtr folder_item; + result = SHCreateItemFromParsingName(initial_directory16.c_str(), nullptr, folder_item.GetIID(), folder_item.InitPtr()); + throw_if_failed(result, "SHCreateItemFromParsingName failed"); + */ + + if (folder_item) + { + result = save_dialog->SetFolder(folder_item); + throw_if_failed(result, "IFileSaveDialog.SetFolder failed"); + } + } + + if (owner && owner->Window()) + result = save_dialog->Show((HWND)owner->Window()->GetNativeHandle()); + else + result = save_dialog->Show(0); + + if (SUCCEEDED(result)) + { + ComPtr chosen_folder; + result = save_dialog->GetResult(chosen_folder.TypedInitPtr()); + throw_if_failed(result, "IFileSaveDialog.GetResult failed"); + + WCHAR* buffer = nullptr; + result = chosen_folder->GetDisplayName(SIGDN_FILESYSPATH, &buffer); + throw_if_failed(result, "IShellItem.GetDisplayName failed"); + + std::wstring output16; + if (buffer) + { + try + { + output16 = buffer; + } + catch (...) + { + CoTaskMemFree(buffer); + throw; + } + } + + CoTaskMemFree(buffer); + filename = from_utf16(output16); + return true; + } + else + { + return false; + } + } + + std::string Filename() const override + { + return filename; + } + + void SetFilename(const std::string& filename) override + { + initial_filename = filename; + } + + void AddFilter(const std::string& filter_description, const std::string& filter_extension) override + { + Filter f; + f.description = filter_description; + f.extension = filter_extension; + filters.push_back(std::move(f)); + } + + void ClearFilters() override + { + filters.clear(); + } + + void SetFilterIndex(int filter_index) override + { + filterindex = filter_index; + } + + void SetInitialDirectory(const std::string& path) override + { + initial_directory = path; + } + + void SetTitle(const std::string& newtitle) override + { + title = newtitle; + } + + void SetDefaultExtension(const std::string& extension) override + { + defaultext = extension; + } + + void throw_if_failed(HRESULT result, const std::string& error) + { + if (FAILED(result)) + throw std::runtime_error(error); + } +}; + +std::unique_ptr SaveFileDialog::Create(Widget* owner) +{ + return std::make_unique(owner); +} + +#else + +std::unique_ptr SaveFileDialog::Create(Widget* owner) +{ + return {}; +} + +#endif diff --git a/Thirdparty/ZWidget/src/widgets/imagebox/imagebox.cpp b/Thirdparty/ZWidget/src/widgets/imagebox/imagebox.cpp index 45d35a16..37253c85 100644 --- a/Thirdparty/ZWidget/src/widgets/imagebox/imagebox.cpp +++ b/Thirdparty/ZWidget/src/widgets/imagebox/imagebox.cpp @@ -5,6 +5,14 @@ ImageBox::ImageBox(Widget* parent) : Widget(parent) { } +double ImageBox::GetPreferredWidth() const +{ + if (image) + return (double)image->GetWidth(); + else + return 0.0; +} + double ImageBox::GetPreferredHeight() const { if (image) diff --git a/Thirdparty/ZWidget/src/widgets/menubar/menubar.cpp b/Thirdparty/ZWidget/src/widgets/menubar/menubar.cpp index 8a66ce8f..51712850 100644 --- a/Thirdparty/ZWidget/src/widgets/menubar/menubar.cpp +++ b/Thirdparty/ZWidget/src/widgets/menubar/menubar.cpp @@ -4,13 +4,214 @@ Menubar::Menubar(Widget* parent) : Widget(parent) { + SetStyleClass("menubar"); } Menubar::~Menubar() { } -void Menubar::OnPaint(Canvas* canvas) +MenubarItem* Menubar::AddItem(std::string text, std::function onOpen, bool alignRight) { - canvas->drawText(Point(16.0, 21.0), Colorf::fromRgba8(0, 0, 0), "File Edit View Tools Window Help"); + auto item = new MenubarItem(this, text, alignRight); + item->SetOpenCallback(std::move(onOpen)); + menuItems.push_back(item); + OnGeometryChanged(); + return item; +} + +void Menubar::ShowMenu(MenubarItem* item) +{ + CloseMenu(); + if (item->GetOpenCallback()) + { + openMenu = new Menu(this); + openMenu->onCloseMenu = [=]() { CloseMenu(); }; + item->GetOpenCallback()(openMenu); + if (item->AlignRight) + openMenu->SetRightPosition(item->MapToGlobal(Point(item->GetWidth(), item->GetHeight()))); + else + openMenu->SetLeftPosition(item->MapToGlobal(Point(0.0, item->GetHeight()))); + openMenu->Show(); + } +} + +void Menubar::CloseMenu() +{ + //delete openMenu; + //openMenu = nullptr; +} + +void Menubar::OnGeometryChanged() +{ + double w = GetWidth(); + double h = GetHeight(); + double left = 0.0; + double right = w; + for (MenubarItem* item : menuItems) + { + double itemwidth = item->GetPreferredWidth(); + itemwidth += 16.0; + if (!item->AlignRight) + { + item->SetFrameGeometry(left, 0.0, itemwidth, h); + left += itemwidth; + } + else + { + right -= itemwidth; + item->SetFrameGeometry(right, 0.0, itemwidth, h); + } + } +} + +///////////////////////////////////////////////////////////////////////////// + +MenubarItem::MenubarItem(Menubar* menubar, std::string text, bool alignRight) : Widget(menubar), menubar(menubar), text(text), AlignRight(alignRight) +{ + SetStyleClass("menubaritem"); +} + +bool MenubarItem::OnMouseDown(const Point& pos, InputKey key) +{ + menubar->ShowMenu(this); + return true; +} + +bool MenubarItem::OnMouseUp(const Point& pos, InputKey key) +{ + return true; +} + +void MenubarItem::OnMouseMove(const Point& pos) +{ + if (GetStyleState().empty()) + { + SetStyleState("hover"); + } +} + +void MenubarItem::OnMouseLeave() +{ + SetStyleState(""); +} + +double MenubarItem::GetPreferredWidth() const +{ + Canvas* canvas = GetCanvas(); + return canvas->measureText(text).width; +} + +void MenubarItem::OnPaint(Canvas* canvas) +{ + double x = (GetWidth() - canvas->measureText(text).width) * 0.5; + canvas->drawText(Point(x, 21.0), GetStyleColor("color"), text); +} + +///////////////////////////////////////////////////////////////////////////// + +Menu::Menu(Widget* parent) : Widget(parent, WidgetType::Popup) +{ + SetStyleClass("menu"); +} + +void Menu::SetLeftPosition(const Point& pos) +{ + SetFrameGeometry(Rect::xywh(pos.x, pos.y, GetPreferredWidth() + GetNoncontentLeft() + GetNoncontentRight(), GetPreferredHeight() + GetNoncontentTop() + GetNoncontentBottom())); +} + +void Menu::SetRightPosition(const Point& pos) +{ + SetFrameGeometry(Rect::xywh(pos.x - GetWidth() - GetNoncontentLeft() - GetNoncontentRight(), pos.y, GetWidth() + GetNoncontentLeft() + GetNoncontentRight(), GetHeight() + GetNoncontentTop() + GetNoncontentBottom())); +} + +MenuItem* Menu::AddItem(std::shared_ptr icon, std::string text, std::function onClick) +{ + auto item = new MenuItem(this); + if (icon) + item->icon->SetImage(icon); + item->text->SetText(text); + /* + item->element->addEventListener("click", [=](Event* event) + { + event->stopPropagation(); + if (onCloseMenu) + onCloseMenu(); + if (onClick) + onClick(); + }); + */ + return item; +} + +MenuItemSeparator* Menu::AddSeparator() +{ + auto sep = new MenuItemSeparator(this); + return sep; +} + +double Menu::GetPreferredWidth() const +{ + return 200.0; +} + +double Menu::GetPreferredHeight() const +{ + double h = 0.0; + for (Widget* item = FirstChild(); item != nullptr; item = item->NextSibling()) + { + h += 20.0; + } + return h; +} + +void Menu::OnGeometryChanged() +{ + double w = GetWidth(); + double h = GetHeight(); + double y = 0.0; + for (Widget* item = FirstChild(); item != nullptr; item = item->NextSibling()) + { + item->SetFrameGeometry(Rect::xywh(0.0, y, w, 20.0)); + y += 20.0; + } +} + +///////////////////////////////////////////////////////////////////////////// + +MenuItem::MenuItem(Widget* parent) : Widget(parent) +{ + SetStyleClass("menuitem"); + icon = new ImageBox(this); + text = new TextLabel(this); +} + +void MenuItem::OnMouseMove(const Point& pos) +{ + if (GetStyleState().empty()) + { + SetStyleState("hover"); + } +} + +void MenuItem::OnMouseLeave() +{ + SetStyleState(""); +} + +void MenuItem::OnGeometryChanged() +{ + double iconwidth = icon->GetPreferredWidth(); + double iconheight = icon->GetPreferredHeight(); + double w = GetWidth(); + double h = GetHeight(); + icon->SetFrameGeometry(Rect::xywh(0.0, (h - iconheight) * 0.5, iconwidth, iconheight)); + text->SetFrameGeometry(Rect::xywh(iconwidth, 0.0, w - iconwidth, h)); +} + +///////////////////////////////////////////////////////////////////////////// + +MenuItemSeparator::MenuItemSeparator(Widget* parent) : Widget(parent) +{ + SetStyleClass("menuitemseparator"); } diff --git a/Thirdparty/ZWidget/src/window/sdl2/sdl2displaywindow.cpp b/Thirdparty/ZWidget/src/window/sdl2/sdl2displaywindow.cpp index 98988cd0..5e44cb6f 100644 --- a/Thirdparty/ZWidget/src/window/sdl2/sdl2displaywindow.cpp +++ b/Thirdparty/ZWidget/src/window/sdl2/sdl2displaywindow.cpp @@ -24,11 +24,14 @@ static void CheckInitSDL() static InitSDL initsdl; } -SDL2DisplayWindow::SDL2DisplayWindow(DisplayWindowHost* windowHost) : WindowHost(windowHost) +SDL2DisplayWindow::SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow) : WindowHost(windowHost) { CheckInitSDL(); - int result = SDL_CreateWindowAndRenderer(320, 200, SDL_WINDOW_HIDDEN /*| SDL_WINDOW_ALLOW_HIGHDPI*/, &WindowHandle, &RendererHandle); + unsigned int flags = SDL_WINDOW_HIDDEN /*| SDL_WINDOW_ALLOW_HIGHDPI*/; + if (popupWindow) + flags |= SDL_WINDOW_BORDERLESS; + int result = SDL_CreateWindowAndRenderer(320, 200, flags, &WindowHandle, &RendererHandle); if (result != 0) throw std::runtime_error(std::string("Unable to create SDL window:") + SDL_GetError()); @@ -174,6 +177,24 @@ Rect SDL2DisplayWindow::GetWindowFrame() const return Rect::xywh(x / uiscale, y / uiscale, w / uiscale, h / uiscale); } +Point SDL2DisplayWindow::MapFromGlobal(const Point& pos) const +{ + int x = 0; + int y = 0; + double uiscale = GetDpiScale(); + SDL_GetWindowPosition(WindowHandle, &x, &y); + return Point(pos.x - x / uiscale, pos.y - y / uiscale); +} + +Point SDL2DisplayWindow::MapToGlobal(const Point& pos) const +{ + int x = 0; + int y = 0; + double uiscale = GetDpiScale(); + SDL_GetWindowPosition(WindowHandle, &x, &y); + return Point(pos.x + x / uiscale, pos.y + y / uiscale); +} + Size SDL2DisplayWindow::GetClientSize() const { int w = 0; @@ -286,11 +307,10 @@ void SDL2DisplayWindow::RunLoop() while (!ExitRunLoop) { - SDL_Event event; + SDL_Event event = {}; int result = SDL_WaitEvent(&event); - if (result == 0) - throw std::runtime_error(std::string("SDL_WaitEvent failed:") + SDL_GetError()); - DispatchEvent(event); + if (result == 1) + DispatchEvent(event); // Silently ignore if it fails and pray it doesn't busy loop, because SDL and Linux utterly sucks! } } diff --git a/Thirdparty/ZWidget/src/window/sdl2/sdl2displaywindow.h b/Thirdparty/ZWidget/src/window/sdl2/sdl2displaywindow.h index cb5c3721..7f54544b 100644 --- a/Thirdparty/ZWidget/src/window/sdl2/sdl2displaywindow.h +++ b/Thirdparty/ZWidget/src/window/sdl2/sdl2displaywindow.h @@ -8,7 +8,7 @@ class SDL2DisplayWindow : public DisplayWindow { public: - SDL2DisplayWindow(DisplayWindowHost* windowHost); + SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow); ~SDL2DisplayWindow(); void SetWindowTitle(const std::string& text) override; @@ -45,6 +45,11 @@ class SDL2DisplayWindow : public DisplayWindow std::string GetClipboardText() override; void SetClipboardText(const std::string& text) override; + Point MapFromGlobal(const Point& pos) const override; + Point MapToGlobal(const Point& pos) const override; + + void* GetNativeHandle() override { return WindowHandle; } + static void DispatchEvent(const SDL_Event& event); static SDL2DisplayWindow* FindEventWindow(const SDL_Event& event); diff --git a/Thirdparty/ZWidget/src/window/win32/win32displaywindow.cpp b/Thirdparty/ZWidget/src/window/win32/win32displaywindow.cpp index 2bf0acff..6d4be07f 100644 --- a/Thirdparty/ZWidget/src/window/win32/win32displaywindow.cpp +++ b/Thirdparty/ZWidget/src/window/win32/win32displaywindow.cpp @@ -56,7 +56,7 @@ static std::wstring to_utf16(const std::string& str) return result; } -Win32DisplayWindow::Win32DisplayWindow(DisplayWindowHost* windowHost) : WindowHost(windowHost) +Win32DisplayWindow::Win32DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow) : WindowHost(windowHost) { Windows.push_front(this); WindowsIterator = Windows.begin(); @@ -74,7 +74,18 @@ Win32DisplayWindow::Win32DisplayWindow(DisplayWindowHost* windowHost) : WindowHo // WS_CAPTION shows the caption (yay! actually a flag that does what it says it does!) // WS_SYSMENU shows the min/max/close buttons // WS_THICKFRAME makes the window resizable - CreateWindowEx(WS_EX_APPWINDOW | WS_EX_DLGMODALFRAME, L"ZWidgetWindow", L"", WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX, 0, 0, 100, 100, 0, 0, GetModuleHandle(0), this); + + DWORD style = 0, exstyle = 0; + if (popupWindow) + { + style = WS_POPUP; + } + else + { + exstyle = WS_EX_APPWINDOW | WS_EX_DLGMODALFRAME; + style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX; + } + CreateWindowEx(exstyle, L"ZWidgetWindow", L"", style, 0, 0, 100, 100, 0, 0, GetModuleHandle(0), this); /* RAWINPUTDEVICE rid; @@ -246,6 +257,26 @@ Rect Win32DisplayWindow::GetWindowFrame() const return Rect(box.left / dpiscale, box.top / dpiscale, box.right / dpiscale, box.bottom / dpiscale); } +Point Win32DisplayWindow::MapFromGlobal(const Point& pos) const +{ + double dpiscale = GetDpiScale(); + POINT point = {}; + point.x = (LONG)std::round(pos.x / dpiscale); + point.y = (LONG)std::round(pos.y / dpiscale); + ScreenToClient(WindowHandle, &point); + return Point(point.x * dpiscale, point.y * dpiscale); +} + +Point Win32DisplayWindow::MapToGlobal(const Point& pos) const +{ + double dpiscale = GetDpiScale(); + POINT point = {}; + point.x = (LONG)std::round(pos.x * dpiscale); + point.y = (LONG)std::round(pos.y * dpiscale); + ClientToScreen(WindowHandle, &point); + return Point(point.x / dpiscale, point.y / dpiscale); +} + Size Win32DisplayWindow::GetClientSize() const { RECT box = {}; diff --git a/Thirdparty/ZWidget/src/window/win32/win32displaywindow.h b/Thirdparty/ZWidget/src/window/win32/win32displaywindow.h index fd7ae3ea..7bb48186 100644 --- a/Thirdparty/ZWidget/src/window/win32/win32displaywindow.h +++ b/Thirdparty/ZWidget/src/window/win32/win32displaywindow.h @@ -14,7 +14,7 @@ class Win32DisplayWindow : public DisplayWindow { public: - Win32DisplayWindow(DisplayWindowHost* windowHost); + Win32DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow); ~Win32DisplayWindow(); void SetWindowTitle(const std::string& text) override; @@ -53,8 +53,13 @@ class Win32DisplayWindow : public DisplayWindow std::string GetClipboardText() override; void SetClipboardText(const std::string& text) override; + Point MapFromGlobal(const Point& pos) const override; + Point MapToGlobal(const Point& pos) const override; + Point GetLParamPos(LPARAM lparam) const; + void* GetNativeHandle() override { return reinterpret_cast(WindowHandle); } + static void ProcessEvents(); static void RunLoop(); static void ExitLoop(); diff --git a/Thirdparty/ZWidget/src/window/window.cpp b/Thirdparty/ZWidget/src/window/window.cpp index 4ceb4946..fa7aaac2 100644 --- a/Thirdparty/ZWidget/src/window/window.cpp +++ b/Thirdparty/ZWidget/src/window/window.cpp @@ -6,9 +6,9 @@ #include "win32/win32displaywindow.h" -std::unique_ptr DisplayWindow::Create(DisplayWindowHost* windowHost) +std::unique_ptr DisplayWindow::Create(DisplayWindowHost* windowHost, bool popupWindow) { - return std::make_unique(windowHost); + return std::make_unique(windowHost, popupWindow); } void DisplayWindow::ProcessEvents() @@ -43,7 +43,7 @@ void DisplayWindow::StopTimer(void* timerID) #elif defined(__APPLE__) -std::unique_ptr DisplayWindow::Create(DisplayWindowHost* windowHost) +std::unique_ptr DisplayWindow::Create(DisplayWindowHost* windowHost, bool popupWindow) { throw std::runtime_error("DisplayWindow::Create not implemented"); } @@ -82,9 +82,9 @@ void DisplayWindow::StopTimer(void* timerID) #include "sdl2/sdl2displaywindow.h" -std::unique_ptr DisplayWindow::Create(DisplayWindowHost* windowHost) +std::unique_ptr DisplayWindow::Create(DisplayWindowHost* windowHost, bool popupWindow) { - return std::make_unique(windowHost); + return std::make_unique(windowHost, popupWindow); } void DisplayWindow::ProcessEvents()