#205 Close button (template, show/hide, enable/disable, events) 4.x-dev

Sat, 28 Nov 2020 11:36:23 +0200

author
cemkalyoncu
date
Sat, 28 Nov 2020 11:36:23 +0200
branch
4.x-dev
changeset 1503
38acd13739cc
parent 1502
e1bd0a8d7cf7
child 1504
f38175bd2065

#205 Close button (template, show/hide, enable/disable, events)

Source/Gorgon/UI/ComponentStack.cpp file | annotate | diff | comparison | revisions
Source/Gorgon/UI/ComponentStack.h file | annotate | diff | comparison | revisions
Source/Gorgon/UI/Template.h file | annotate | diff | comparison | revisions
Source/Gorgon/UI/Widget.h file | annotate | diff | comparison | revisions
Source/Gorgon/Widgets/Generator.cpp file | annotate | diff | comparison | revisions
Source/Gorgon/Widgets/Panel.cpp file | annotate | diff | comparison | revisions
Source/Gorgon/Widgets/Window.cpp file | annotate | diff | comparison | revisions
Source/Gorgon/Widgets/Window.h file | annotate | diff | comparison | revisions
Testing/Source/Manual/UI_Generate.cpp file | annotate | diff | comparison | revisions
--- a/Source/Gorgon/UI/ComponentStack.cpp	Sat Nov 28 07:37:42 2020 +0200
+++ b/Source/Gorgon/UI/ComponentStack.cpp	Sat Nov 28 11:36:23 2020 +0200
@@ -1030,7 +1030,7 @@
         handlingmouse = true;
     }
 
-    bool ComponentStack::TagHasSubStack(ComponentTemplate::Tag tag) const {
+    bool ComponentStack::TagHasSubstack(ComponentTemplate::Tag tag) const {
         auto comp = gettag(tag);
         
         if(!comp)
@@ -1039,6 +1039,15 @@
             return substacks.Exists(&comp->GetTemplate());
     }
 
+    ComponentStack &ComponentStack::GetTagSubstack(ComponentTemplate::Tag tag) const {
+        auto comp = gettag(tag);
+        
+        if(!comp)
+            throw std::runtime_error("Tag does not exist");
+        else
+            return substacks[&comp->GetTemplate()];
+    }
+
     std::array<float, 4> ComponentStack::CoordinateToValue(ComponentTemplate::Tag tag, Geometry::Point location, bool relative) {
         if(updaterequired)
             update();
--- a/Source/Gorgon/UI/ComponentStack.h	Sat Nov 28 07:37:42 2020 +0200
+++ b/Source/Gorgon/UI/ComponentStack.h	Sat Nov 28 11:36:23 2020 +0200
@@ -295,7 +295,11 @@
         /// Returns whether the component marked with the tag has a substack. If multiple components
         /// are marked to have substack, only the first one is considered. If the tag does not exist
         /// this function will return false.
-        bool TagHasSubStack(ComponentTemplate::Tag tag) const;
+        bool TagHasSubstack(ComponentTemplate::Tag tag) const;
+        
+        /// Returns the substack that the component with the given tag has. If it does not exit, this
+        /// function will throw. Use TagHasSubStack to ensure the tag has a substack.
+        ComponentStack &GetTagSubstack(ComponentTemplate::Tag tag) const;
         
         /// Translates the given coordinates back to values using value scaling and channel mapping.
         /// Only works if the value affects the component location or size. If component with the
@@ -549,6 +553,7 @@
         
         /// @}
 
+        
         Event<ComponentStack> ConditionChanged;
         
         
--- a/Source/Gorgon/UI/Template.h	Sat Nov 28 07:37:42 2020 +0200
+++ b/Source/Gorgon/UI/Template.h	Sat Nov 28 11:36:23 2020 +0200
@@ -307,6 +307,9 @@
         /// This widget is the cancel widget of its container
         Cancel,
         
+        /// This widget is hidden
+        Hidden,
+        
         
         
         /// Do not use this value
@@ -894,7 +897,8 @@
             ItemTag,
             HeaderTag,
             SpacerTag,
-            ListTag
+            ListTag,
+            CloseTag,
         };
         
         /// Some components are repeated along some axis, this property controls how they will be
@@ -1392,7 +1396,12 @@
             sizingw = Automatic;
             sizingh = Automatic;
         }
-
+        
+        virtual ~PlaceholderTemplate() {
+            if(owned)
+                delete temp;
+        }
+        
         /// Returns the type of the component.
         virtual ComponentType GetType() const noexcept override {
             return ComponentType::Placeholder;
@@ -1400,10 +1409,23 @@
         
         /// Sets the sub template for this placeholder.
         void SetTemplate(const Template &value) {
+            if(&value == temp)
+                return;
+            
+            if(owned)
+                delete temp;
+            
             temp = &value;
             ChangedEvent();
         }
         
+        /// Sets the sub template for this placeholder.
+        void OwnTemplate(const Template &value) {
+            temp = &value;
+            owned = true;
+            ChangedEvent();
+        }
+        
         /// Returns if this placeholder has a sub template
         bool HasTemplate() const {
             return temp != nullptr;
@@ -1419,6 +1441,7 @@
         
     private:
         const Template *temp = nullptr;
+        bool owned = false;
     };
     
     /// This component type will be ignored by ComponentStack, effectively erasing
--- a/Source/Gorgon/UI/Widget.h	Sat Nov 28 07:37:42 2020 +0200
+++ b/Source/Gorgon/UI/Widget.h	Sat Nov 28 11:36:23 2020 +0200
@@ -101,7 +101,7 @@
         bool IsFocused() const { return focus; }
         
         /// Shows this widget, widgets are visible by default.
-        void Show() { SetVisible(true); }
+        virtual void Show() { SetVisible(true); }
         
         /// Hides this widget, when hidden, widgets cannot gain focus
         void Hide() { SetVisible(false); }
--- a/Source/Gorgon/Widgets/Generator.cpp	Sat Nov 28 07:37:42 2020 +0200
+++ b/Source/Gorgon/Widgets/Generator.cpp	Sat Nov 28 11:36:23 2020 +0200
@@ -1876,7 +1876,7 @@
         cbg.SetSize(100, 100, UI::Dimension::Percent);
         cbg.SetPositioning(UI::ComponentTemplate::Relative);
         cbg.SetAnchor(UI::Anchor::BottomCenter, UI::Anchor::TopCenter, UI::Anchor::TopCenter);
-            
+        
         auto addborder = [&](auto condition, auto &image) {
             auto &bg = tmp.AddContainer(0, condition)
                 .AddIndex(5) //title
@@ -1891,13 +1891,107 @@
         addborder(UI::ComponentCondition::Always, PassiveWindowBorder());
         addborder(UI::ComponentCondition::Focused, ActiveWindowBorder());
         
-        auto &text = tmp.AddTextholder(5, UI::ComponentCondition::Always);
+        auto &titlebar = tmp.AddContainer(5, UI::ComponentCondition::Always)
+            .AddIndex(7) //icon
+            .AddIndex(8) //text
+            .AddIndex(9) //close button
+        ;
+        titlebar.SetTag(UI::ComponentTemplate::DragTag);
+        titlebar.SetSize({100, UI::Dimension::Percent}, WidgetHeight);
+        //titlebar.Background.SetAnimation(NormalBG());
+        titlebar.SetMargin(0, 0, 0, Border.Width);
+        
+        auto &icon = tmp.AddGraphics(7, UI::ComponentCondition::Always);
+        icon.SetDataEffect(UI::ComponentTemplate::Icon);
+        icon.SetAnchor(UI::Anchor::None, UI::Anchor::MiddleLeft, UI::Anchor::MiddleLeft);
+        
+        auto &text = tmp.AddTextholder(8, UI::ComponentCondition::Always);
         text.SetRenderer(CenteredFont);
-        text.SetSize({100, UI::Dimension::Percent}, WidgetHeight);
-        text.SetMargin(0, 0, 0, Border.Width);
+        text.SetSize(100, 100, UI::Dimension::Percent);
         text.SetDataEffect(UI::ComponentTemplate::Title);
         text.SetSizing(UI::ComponentTemplate::Fixed);
         text.SetTag(UI::ComponentTemplate::DragTag);
+        text.SetAnchor(UI::Anchor::MiddleRight, UI::Anchor::MiddleLeft, UI::Anchor::MiddleLeft);
+        
+        auto &cb = tmp.AddPlaceholder(9, UI::ComponentCondition::Always);
+        cb.SetSize(ObjectHeight, ObjectHeight);
+        cb.SetSizing(UI::ComponentTemplate::Fixed);
+        cb.SetTag(UI::ComponentTemplate::CloseTag);
+        cb.SetAnchor(UI::Anchor::MiddleRight, UI::Anchor::MiddleLeft, UI::Anchor::MiddleLeft);
+        
+        UI::Template &closebtn = *new UI::Template;
+        closebtn.SetSize(ObjectHeight, ObjectHeight);
+        closebtn.SetSpacing(Spacing);
+        closebtn.SetUnitWidth(BorderedWidgetHeight);
+        closebtn.AddContainer(0, UI::ComponentCondition::Always)
+            .AddIndex(1)
+            .Background.SetAnimation(NormalBorder())
+        ;
+        closebtn.AddContainer(0, UI::ComponentCondition::Hover)
+            .AddIndex(1)
+            .Background.SetAnimation(HoverBorder())
+        ;
+        closebtn.AddContainer(0, UI::ComponentCondition::Down)
+            .AddIndex(1)
+            .Background.SetAnimation(DownBorder())
+        ;
+        closebtn.AddContainer(0, UI::ComponentCondition::Disabled)
+            .AddIndex(1)
+            .Background.SetAnimation(DisabledBorder())
+        ;
+        closebtn.AddContainer(0, UI::ComponentCondition::Hidden);
+        
+        auto cross = [&](auto color) {
+            int bs = int(std::round(ObjectHeight*0.6));
+            if(bs%2 != ObjectHeight%2)
+                bs++;
+            
+            auto icon = new Graphics::Bitmap({bs, bs});
+            
+            icon->Clear();
+            
+            float off = ObjectBorder;
+            float s = float(bs);
+            float mid = s/2.f;
+            
+            Geometry::PointList<Geometry::Pointf> border = {
+                {off, 0},
+                {mid,mid-off},
+                {s-off, 0},
+                {s, off},
+                {mid+off, mid},
+                {s, s-off},
+                {s-off, s},
+                {mid, mid+off},
+                {off, s},
+                {0, s-off},
+                {mid-off, mid},
+                {0, off},
+                {off, 0}
+            };
+            
+            CGI::Polyfill(*icon, border, CGI::SolidFill<>(color));
+            icon->Prepare();
+            drawables.Add(icon);
+            
+            return icon;
+        };
+        
+        {
+        auto &sym = closebtn.AddGraphics(1, UI::ComponentCondition::Always);
+        sym.Content.SetAnimation(*cross(Forecolor.Regular));
+        sym.SetAnchor(UI::Anchor::None, UI::Anchor::MiddleCenter, UI::Anchor::MiddleCenter);
+        }
+        
+        {
+        auto &sym = closebtn.AddGraphics(1, UI::ComponentCondition::Disabled);
+        sym.Content.SetAnimation(*cross(Forecolor.Regular));
+        sym.SetAnchor(UI::Anchor::None, UI::Anchor::MiddleCenter, UI::Anchor::MiddleCenter);
+        sym.SetColor({1.0f, 0.5f});
+        }
+        
+        cb.OwnTemplate(closebtn);
+        
         
         return tmp;
     }
--- a/Source/Gorgon/Widgets/Panel.cpp	Sat Nov 28 07:37:42 2020 +0200
+++ b/Source/Gorgon/Widgets/Panel.cpp	Sat Nov 28 11:36:23 2020 +0200
@@ -12,6 +12,7 @@
         },
     }) 
     {
+        stack.HandleMouse();
         stack.SetOtherMouseEvent([this](UI::ComponentTemplate::Tag, Input::Mouse::EventType type, Geometry::Point location, float amount) {
             return MouseScroll(type, location, amount);
         });
--- a/Source/Gorgon/Widgets/Window.cpp	Sat Nov 28 07:37:42 2020 +0200
+++ b/Source/Gorgon/Widgets/Window.cpp	Sat Nov 28 11:36:23 2020 +0200
@@ -1,11 +1,13 @@
 #include "Window.h"
 
+#include "../Graphics/Bitmap.h"
 
 namespace Gorgon { namespace Widgets {
    
     Window::Window(const UI::Template &temp, const std::string &title) : 
         Panel(temp),
         Title(this),
+        Icon(this),
         title(title)
     {
         stack.SetData(UI::ComponentTemplate::Title, title);
@@ -13,6 +15,7 @@
         stack.SetMouseDownEvent([this](auto tag, auto location, auto button) { mouse_down(tag, location, button); });
         stack.SetMouseUpEvent([this](auto tag, auto location, auto button) { mouse_up(tag, location, button); });
         stack.SetMouseMoveEvent([this](auto tag, auto location) { mouse_move(tag, location); });
+        stack.SetClickEvent([this](auto tag, auto location, auto button) { mouse_click(tag, location, button); });
         
         updatescrollvisibility();
     }
@@ -22,6 +25,65 @@
         stack.SetData(UI::ComponentTemplate::Title, title);
     }
     
+	void Window::SetIcon(const Graphics::Animation &value) {
+        if(ownicon) {
+            icon->DeleteAnimation();;
+        }
+        delete iconprov;
+        
+        icon = &value;
+        iconprov = nullptr;
+        stack.SetData(UI::ComponentTemplate::Icon, *icon);
+        
+        ownicon = false;
+    }
+    
+    
+    void Window::SetIcon(const Graphics::Bitmap& value){
+        SetIcon(dynamic_cast<const Graphics::Animation&>(value));
+    }
+    
+    
+    void Window::SetIconProvider(const Graphics::AnimationProvider &value) {
+        auto &anim = value.CreateAnimation(true);
+        
+        OwnIcon(anim);
+    }
+    
+    void Window::SetIconProvider(Graphics::AnimationProvider &&provider) {
+        iconprov = &(provider.MoveOutProvider());
+        auto &anim = iconprov->CreateAnimation(true);
+        
+        OwnIcon(anim);
+    }
+    
+    void Window::RemoveIcon() {
+        if(ownicon) {
+            icon->DeleteAnimation();
+        }
+        delete iconprov;
+        
+        icon = nullptr;
+        
+        stack.RemoveData(UI::ComponentTemplate::Icon);
+    }
+    
+    
+    void Window::OwnIcon() {
+        ownicon = true;
+    }
+    
+    
+    void Window::OwnIcon(const Graphics::Animation &value) {
+        SetIcon(value);
+        
+        ownicon = true;
+    }
+    
+    void Window::OwnIcon(Graphics::Bitmap &&value) {
+        OwnIcon(*new Graphics::Bitmap(std::move(value)));
+    }
+    
     void Window::EnableScroll(bool vertical, bool horizontal) {
         Panel::EnableScroll(vertical, horizontal);
         
@@ -87,7 +149,23 @@
         }
     }
     
-
+    void Window::mouse_click(UI::ComponentTemplate::Tag tag, Geometry::Point location, Input::Mouse::Button button) {
+        if(tag == UI::ComponentTemplate::NoTag) {
+            if(stack.IndexOfTag(UI::ComponentTemplate::DragTag) == -1)
+                tag = UI::ComponentTemplate::DragTag;
+            else {
+                int ind = stack.ComponentAt(location);
+                
+                if(ind != -1)
+                    tag = stack.GetTemplate(ind).GetTag();
+            }
+        }
+        
+        if(button == Input::Mouse::Button::Left && tag == UI::ComponentTemplate::CloseTag) {
+            Close();
+        }
+    }
+    
     void Window::AllowMovement(bool allow) {
         if(allowmove == allow) 
             return;
@@ -96,5 +174,44 @@
         if(moving)
             mouse_up(UI::ComponentTemplate::NoTag, {0, 0}, Input::Mouse::Button::Left);
     }
+    
+    void Window::SetCloseButtonVisibility(bool value) {
+        if(closebutton == value)
+            return;
+        
+        closebutton = value;
+        
+        if(stack.TagHasSubstack(UI::ComponentTemplate::CloseTag)) {
+            if(closebutton) {
+                if(enableclose)
+                    stack.GetTagSubstack(UI::ComponentTemplate::CloseTag).RemoveCondition(UI::ComponentCondition::Disabled);
+                else
+                    stack.GetTagSubstack(UI::ComponentTemplate::CloseTag).AddCondition(UI::ComponentCondition::Disabled);
+                
+                stack.GetTagSubstack(UI::ComponentTemplate::CloseTag).RemoveCondition(UI::ComponentCondition::Hidden);
+            }
+            else {
+                stack.GetTagSubstack(UI::ComponentTemplate::CloseTag).AddCondition(UI::ComponentCondition::Disabled);
+                stack.GetTagSubstack(UI::ComponentTemplate::CloseTag).AddCondition(UI::ComponentCondition::Hidden);
+            }
+        }
+    }
+
+    void Window::SetCloseButtonEnabled(bool value) {
+        if(enableclose == value)
+            return;
+        
+        enableclose = value;
+        
+        if(stack.TagHasSubstack(UI::ComponentTemplate::CloseTag)) {
+            if(closebutton) {
+                if(enableclose)
+                    stack.GetTagSubstack(UI::ComponentTemplate::CloseTag).RemoveCondition(UI::ComponentCondition::Disabled);
+                else
+                    stack.GetTagSubstack(UI::ComponentTemplate::CloseTag).AddCondition(UI::ComponentCondition::Disabled);
+            }
+        }
+    }
+
 
 } }
--- a/Source/Gorgon/Widgets/Window.h	Sat Nov 28 07:37:42 2020 +0200
+++ b/Source/Gorgon/Widgets/Window.h	Sat Nov 28 11:36:23 2020 +0200
@@ -2,6 +2,8 @@
 
 #include "Panel.h"
 
+namespace Gorgon { namespace Graphics { class Bitmap; } }
+
 namespace Gorgon { namespace Widgets {
     
     /**
@@ -33,6 +35,48 @@
         std::string GetTitle() const {
             return title;
         }
+                
+        /// Changes the icon on the label. The ownership of the bitmap
+        /// is not transferred. If you wish the bitmap to be destroyed
+        /// with the label, use OwnIcon instead.
+        void SetIcon(const Graphics::Bitmap &value);
+        
+        /// Changes the icon on the label. The ownership of the animation
+        /// is not transferred. If you wish the animation to be destroyed
+        /// with the label, use OwnIcon instead.
+        void SetIcon(const Graphics::Animation &value);
+        
+        /// Changes the icon on the label. This will create a new animation
+        /// from the given provider and will own the resultant animation.
+        void SetIconProvider(const Graphics::AnimationProvider &value);
+        
+        /// Changes the icon on the label. This will move in the provider,
+        /// create a new animation and own both the provider and the animation
+        void SetIconProvider(Graphics::AnimationProvider &&provider);
+        
+        /// Removes the icon on the label
+        void RemoveIcon();
+        
+        /// Returns if the label has an icon
+        bool HasIcon() const { return icon != nullptr; }
+        
+        /// Returns the icon on the label. If the label does not have an
+        /// icon, this function will throw
+        const Graphics::Animation &GetIcon() const {
+            if(!HasIcon())
+                throw std::runtime_error("This widget has no icon.");
+            
+            return *icon;
+        }
+        
+        /// Transfers the ownership of the current icon.
+        void OwnIcon();
+        
+        /// Sets the icon while transferring the ownership
+        void OwnIcon(const Graphics::Animation &value);
+        
+        /// Moves the given animation to the icon of the label
+        void OwnIcon(Graphics::Bitmap &&value);
         
         /// Locks the movement of the window.
         void LockMovement() {
@@ -47,9 +91,75 @@
             return allowmove;
         }
         
+        /// Hides the close button. Close button is visible by default.
+        void HideCloseButton() {
+            SetCloseButtonVisibility(false);
+        }
+        
+        /// Shows the close button. Close button is visible by default.
+        void ShowCloseButton() {
+            SetCloseButtonVisibility(true);
+        }
+        
+        /// Sets close button visibility. Close button is visible by default.
+        void SetCloseButtonVisibility(bool value);
+        
+        /// Returns if the close button is visible
+        bool IsCloseButtonVisible() const {
+            return closebutton;
+        }
+        
+        /// Disables the close button. Close button is enabled by default.
+        void DisableCloseButton() {
+            SetCloseButtonEnabled(false);
+        }
+        
+        /// Enables the close button. Close button is enabled by default.
+        void EnableCloseButton() {
+            SetCloseButtonEnabled(true);
+        }
+        
+        /// Sets whether close button is enabled. Close button is enabled by default.
+        void SetCloseButtonEnabled(bool value);
+        
+        /// Returns if the close button is enabled
+        bool IsCloseButtonEnabled() const {
+            return enableclose;
+        }
+        
+        /// Hides the window if it is not canceled. Calling hide will cause
+        /// the window to become hidden without any chance of preventing it.
+        void Close() {
+            bool allow = true;
+            ClosingEvent(allow);
+            if(allow) {
+                Hide();
+                CloseEvent();
+            }
+        }
+        
+        virtual void Show() override {
+            UI::ComponentStackWidget::Show();
+            Focus();
+        }
+        
         virtual void EnableScroll(bool vertical, bool horizontal) override;
         
+        /// Window title
         TextualProperty<Window, std::string, &Window::GetTitle, &Window::SetTitle> Title;
+        
+        /// Icon that will be displayed on the window. Availability of icon depends
+        /// on the theme.
+        ObjectProperty<Window, const Graphics::Animation, &Window::GetIcon, &Window::SetIcon> Icon;
+        
+        /// This event is called after the window is closed. In Gorgon, close button
+        /// only hides the window. It is the owner's task to cleanup. Set the bool
+        /// parameter to false to prevent window from closing.
+        Event<Window, bool& /*allow = true*/> ClosingEvent = Event<Window, bool&>{this};
+        
+        /// This event is called after the window is closed. In Gorgon, close button
+        /// only hides the window. It is the owner's task to cleanup.
+        Event<Window> CloseEvent = Event<Window>{this};
 
     protected:
         virtual void updatescroll() override;
@@ -64,8 +174,16 @@
         
         void mouse_move(UI::ComponentTemplate::Tag tag, Geometry::Point location);
         
+        void mouse_click(UI::ComponentTemplate::Tag tag, Geometry::Point location, Input::Mouse::Button button);
+        
     private:
         std::string title;
+        const Graphics::Animation          *icon     = nullptr;
+        const Graphics::AnimationProvider  *iconprov = nullptr;
+        
+        bool closebutton = true;
+        bool enableclose = true;
+        bool ownicon = false;
         bool allowmove = true;
         bool moving = false;
         Geometry::Point dragoffset;
--- a/Testing/Source/Manual/UI_Generate.cpp	Sat Nov 28 07:37:42 2020 +0200
+++ b/Testing/Source/Manual/UI_Generate.cpp	Sat Nov 28 11:36:23 2020 +0200
@@ -299,7 +299,18 @@
     wind.SetTitle("My window");
     app.wind.Add(wind);
     wind.Add(btn);
+    wind.OwnIcon(icon.CreateAnimation());
+    int closetrycount = 0;
+    wind.ClosingEvent.Register([&](bool &allow) {
+        allow = closetrycount++;
+        if(!allow)
+            std::cout << "Click once more to close." << std::endl;
+    });
     btn.Move(0,0);
+    Widgets::Checkbox enableclosebtn("Enable close button", true);
+    enableclosebtn.ChangedEvent.Register([&] { wind.SetCloseButtonEnabled(enableclosebtn); });
+    wind.Add(enableclosebtn);
+    wind.CreateOrganizer<UI::Organizers::List>();
 
     /*Widgets::Progressor<std::string, StringDiv, StringVal, Gorgon::TextualProperty> bar2;
     bar2.Maximum = "Hello world";

mercurial