import elements.BaseGuiElement; import elements.GuiScrollbar; export GuiPanel, ScrollType; enum ScrollType { ST_Always, ST_Never, ST_Auto, }; class GuiPanel : BaseGuiElement { recti RealAbsolutePosition; vec2i virtualSize; vec2d scrollOffset; vec2i Origin; recti minPanelSize; recti sizePadding; ScrollType horizType; ScrollType vertType; GuiScrollbar@ horiz; GuiScrollbar@ vert; bool ScrollPane = false; bool ShowPaneScroll = false; int dragThreshold = 5; bool allowScrollDrag = true; vec2i dragStart; bool heldLeft = false; bool isDragging = false; bool animating = false; bool LeftDrag = true; bool MiddleDrag = true; GuiPanel(IGuiElement@ Parent, const recti& Rectangle) { super(Parent, Rectangle); _GuiPanel(); } GuiPanel(IGuiElement@ Parent, Alignment@ Align) { super(Parent, Align); _GuiPanel(); } void _GuiPanel() { @horiz = GuiScrollbar(null, recti()); horiz.orientation = SO_Horizontal; horiz.scroll = 40; horiz.alignment.left.set(AS_Left, 0.0, 0); horiz.alignment.right.set(AS_Right, 0.0, 0); horiz.alignment.top.set(AS_Bottom, 0.0, 0); horiz.alignment.bottom.set(AS_Bottom, 0.0, -20); @vert = GuiScrollbar(null, recti()); vert.scroll = 40; vert.alignment.left.set(AS_Right, 0.0, 0); vert.alignment.right.set(AS_Right, 0.0, -20); vert.alignment.top.set(AS_Top, 0.0, 0); vert.alignment.bottom.set(AS_Bottom, 0.0, 0); horizType = ST_Auto; vertType = ST_Auto; @horiz.parent = this; @vert.parent = this; } void addChild(IGuiElement@ ele) { BaseGuiElement::addChild(ele); updateAbsolutePosition(); } void removeChild(IGuiElement@ ele) { BaseGuiElement::removeChild(ele); updateAbsolutePosition(); } void scrollToVert(int position) { vert.pos = clamp(position, vert.start, vert.end-vert.page); updateAbsolutePosition(); } void setScrollPane(bool value, bool showScrollbars = false) { ShowPaneScroll = showScrollbars; if(ScrollPane == value) return; ScrollPane = value; scrollOffset = vec2d(); updateAbsolutePosition(); } void center() { updateAbsolutePosition(); scrollOffset = (vec2d(virtualSize) - vec2d(size)) / 2 + vec2d(Origin); updateAbsolutePosition(); } void centerAround(const vec2i& pos) { updateAbsolutePosition(); scrollOffset = vec2d(pos) - vec2d(size) / 2; updateAbsolutePosition(); } void stopDrag() { heldLeft = false; isDragging = false; dragStart = vec2i(); } bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) { if(ScrollPane) { if(source is this || isAncestorOf(source)) { switch(event.type) { case MET_Button_Down: if((LeftDrag && event.button == 0) || (MiddleDrag && event.button == 2)) { heldLeft = true; dragStart = mousePos; return true; } break; case MET_Moved: { vec2i d = mousePos - dragStart; if(isDragging) { scroll(d.x, d.y); dragStart = mousePos; } else if(heldLeft) { if(abs(d.x) >= dragThreshold || abs(d.y) >= dragThreshold) { isDragging = true; scroll(d.x, d.y); dragStart = mousePos; } } } break; case MET_Button_Up: if((LeftDrag && event.button == 0) || (MiddleDrag && event.button == 2)) { heldLeft = false; dragStart = vec2i(); if(isDragging) { isDragging = false; return true; } } break; } } if(event.type == MET_Scrolled && allowScrollDrag) { if(virtualSize.height > size.height) { if(ctrlKey && virtualSize.width > size.width) scroll(event.y * 30, 0); else scroll(0, event.y * 30); return true; } else if(virtualSize.width > size.width) { scroll(event.y * 30, 0); return true; } } } else { if(event.type == MET_Scrolled) { if(horiz.visible && (horiz.AbsolutePosition.isWithin(mousePos) || !vert.visible)) return horiz.onMouseEvent(event, horiz); else if(vert.visible) return vert.onMouseEvent(event, vert); } } return BaseGuiElement::onMouseEvent(event, source); } bool onKeyEvent(const KeyboardEvent& event, IGuiElement@ source) { if(source !is horiz && source !is vert) { switch(event.key) { case KEY_LEFT: case KEY_RIGHT: if(horiz.visible) return horiz.onKeyEvent(event, horiz); break; case KEY_UP: case KEY_DOWN: case KEY_PAGEUP: case KEY_PAGEDOWN: if(vert.visible) return vert.onKeyEvent(event, vert); break; } } return BaseGuiElement::onKeyEvent(event, source); } bool onGuiEvent(const GuiEvent& event) { if(event.type == GUI_Changed) { if(event.caller is vert || event.caller is horiz) { updateAbsolutePosition(); return true; } } return BaseGuiElement::onGuiEvent(event); } bool get_onScreen() const { return RealAbsolutePosition.overlaps(recti(vec2i(), screenSize)); } void scroll(double dx, double dy) { vec2i delta = vec2i(scrollOffset); scrollOffset -= vec2d(dx, dy); scrollOffset.x = clamp(scrollOffset.x, double(Origin.x), max(double(virtualSize.width - size.width + Origin.x), double(Origin.x))); scrollOffset.y = clamp(scrollOffset.y, double(Origin.y), max(double(virtualSize.height - size.height + Origin.y), double(Origin.y))); delta -= vec2i(scrollOffset); if(delta.x != 0 || delta.y != 0) { AbsolutePosition += delta; for(uint i = 0, cnt = Children.length; i < cnt; ++i) { auto@ chld = Children[i]; if(chld is vert || chld is horiz) continue; chld.abs_move(delta); } } if(ShowPaneScroll) { horiz.pos = scrollOffset.x - Origin.x; vert.pos = scrollOffset.y - Origin.y; } } void setAnimating(bool value) { animating = value; } void updateAbsolutePosition() { if(horiz is null || vert is null) return; //Resize actual panel if(Parent !is null) { if(Alignment !is null) { Position = Alignment.resolve(Parent.absolutePosition.size); ClipRect = recti(vec2i(), Position.size); } AbsolutePosition = Position + Parent.absolutePosition.topLeft; AbsoluteClipRect = (ClipRect + AbsolutePosition.topLeft).clipAgainst(Parent.absoluteClipRect); } else { if(Alignment !is null) { Position = Alignment.resolve(screenSize); ClipRect = recti(vec2i(), Position.size); } AbsolutePosition = Position; AbsoluteClipRect = ClipRect + Position.topLeft; } //Figure out virtual size of canvas uint cCnt = Children.length(); Origin = AbsolutePosition.topLeft + minPanelSize.topLeft; vec2i realSize = AbsolutePosition.size; virtualSize = minPanelSize.size; if(vert.visible) AbsolutePosition.botRight.x -= 20; if(horiz.visible) AbsolutePosition.botRight.y -= 20; for(uint i = 0; i != cCnt; ++i) { IGuiElement@ child = Children[i]; if(child is horiz || child is vert || !child.visible) continue; child.updateAbsolutePosition(); vec2i topLeft = child.absolutePosition.topLeft; if(topLeft.x < Origin.x) Origin.x = topLeft.x; if(topLeft.y < Origin.y) Origin.y = topLeft.y; } for(uint i = 0; i != cCnt; ++i) { IGuiElement@ child = Children[i]; if(child is horiz || child is vert || !child.visible) continue; vec2i botRight = child.absolutePosition.botRight; virtualSize.width = max(virtualSize.width, botRight.x - Origin.x); virtualSize.height = max(virtualSize.height, botRight.y - Origin.y); } Origin -= sizePadding.topLeft; virtualSize += sizePadding.topLeft; virtualSize += sizePadding.botRight; //Update scrollbars Origin -= AbsolutePosition.topLeft; if(!ScrollPane || ShowPaneScroll) { horiz.end = virtualSize.width; horiz.page = AbsolutePosition.width; horiz.bar = horiz.page; horiz.pos = max(horiz.start, min(horiz.end-horiz.page, horiz.pos)); vert.end = virtualSize.height; vert.page = AbsolutePosition.height; vert.bar = vert.page; vert.pos = max(vert.start, min(vert.end-vert.page, vert.pos)); scrollOffset = vec2d(horiz.pos + Origin.x, vert.pos + Origin.y); //Check which scrollbars should be visible bool prevHoriz = horiz.visible; bool prevVert = vert.visible; if(animating) { vert.visible = false; horiz.visible = false; } else { vert.visible = (virtualSize.height > realSize.height && vertType != ST_Never) || vertType == ST_Always; horiz.visible = (virtualSize.width > realSize.width && horizType != ST_Never) || horizType == ST_Always; if(horiz.visible && !vert.visible) vert.visible = (virtualSize.height > realSize.height - 20 && vertType != ST_Never) || vertType == ST_Always; } if(prevHoriz != horiz.visible || prevVert != vert.visible) { updateAbsolutePosition(); return; } //Update scrollbar positions horiz.updateAbsolutePosition(); vert.updateAbsolutePosition(); } else { scroll(0, 0); horiz.visible = false; vert.visible = false; } //Fake absolute position and reposition children RealAbsolutePosition = AbsolutePosition; AbsolutePosition -= vec2i(scrollOffset); if(horiz.visible) AbsoluteClipRect.botRight.y -= 20; if(vert.visible) AbsoluteClipRect.botRight.x -= 20; for(uint i = 0; i != cCnt; ++i) { if(Children[i] is horiz || Children[i] is vert) continue; Children[i].updateAbsolutePosition(); } } void move(const vec2i& moveBy) { Position += moveBy; AbsolutePosition += moveBy; RealAbsolutePosition += moveBy; AbsoluteClipRect = ClipRect + RealAbsolutePosition.topLeft; if(Parent !is null) AbsoluteClipRect = AbsoluteClipRect.clipAgainst(Parent.absoluteClipRect); uint cCnt = Children.length(); for(uint i = 0; i != cCnt; ++i) Children[i].abs_move(moveBy); } void abs_move(const vec2i& moveBy) { AbsolutePosition += moveBy; RealAbsolutePosition += moveBy; AbsoluteClipRect = ClipRect + RealAbsolutePosition.topLeft; if(Parent !is null) AbsoluteClipRect = AbsoluteClipRect.clipAgainst(Parent.absoluteClipRect); uint cCnt = Children.length(); for(uint i = 0; i != cCnt; ++i) Children[i].abs_move(moveBy); } void set_horizPosition(int pos) { horiz.pos = min(double(pos), horiz.end - horiz.page); updateAbsolutePosition(); } void set_vertPosition(int pos) { vert.pos = min(double(pos), vert.end - vert.page); updateAbsolutePosition(); } recti get_absolutePosition() { return RealAbsolutePosition; } void draw() { if(!visible) return; clearClip(); if(horiz.visible) horiz.draw(); if(vert.visible) vert.draw(); uint cCnt = Children.length(); for(uint i = 0; i != cCnt; ++i) { if(Children[i] is horiz || Children[i] is vert) continue; IGuiElement@ ele = Children[i]; if(!ele.visible || !RealAbsolutePosition.overlaps(ele.absolutePosition)) continue; if(ele.noClip) clearClip(); else setClip(ele.absoluteClipRect); ele.draw(); if(!ele.noClip) clearClip(); } } };