//+------------------------------------------------------------------+ //| UWindow.mqh | //| Copyright 2026, DearMQL5 GUI Engine | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, DearMQL5 GUI Engine" #property link "https://www.mql5.com" #property strict #include "..\Core\UTheme.mqh" #include "..\Core\UInputState.mqh" #include "..\Core\UState.mqh" #include "..\Core\UIdStack.mqh" #include "..\Core\ULayout.mqh" #include "..\Util\UMath.mqh" #include "UWindowManager.mqh" #include "..\Widgets\UWidget.mqh" #ifdef UI_BACKEND_CANVAS #include "..\Render\URendererCanvas.mqh" #endif // Window flags bitmask enum ENUM_U_WINDOW_FLAGS { U_WINDOW_NONE = 0, U_WINDOW_NO_TITLEBAR = 1 << 0, U_WINDOW_NO_MOVE = 1 << 1, U_WINDOW_NO_COLLAPSE = 1 << 2, U_WINDOW_NO_CLOSE = 1 << 3, U_WINDOW_AUTO_RESIZE = 1 << 4 }; /// Immediate-mode window container supporting dragging, collapsing, and clipping. class UWindow { private: static ulong s_current_win_id; static ulong s_dragging_win_id; static ulong s_candidate_drag_win_id; static int s_anchor_mouse_x; static int s_anchor_mouse_y; static int s_anchor_win_x; static int s_anchor_win_y; static string s_current_title; public: /// Processes window lifecycle and returns whether the window body should be drawn. static bool Begin(const string title, bool &io_open, int default_x, int default_y, int default_w, int default_h, const uint flags, int &out_x, int &out_y, int &out_w, int &out_h, bool &out_collapsed) { if(!io_open) return(false); ulong win_id = UIdStack::GetId(title); UIdStack::Push(title); s_current_win_id = win_id; s_current_title = title; SUTheme th = UTheme::Get(); // Retrieve persistent position and dimensions from retained state // If uninitialized in UState, try restoring from GlobalVariables via UWindowManager if(UState::GetInt(win_id + 1, -999999) == -999999) { int saved_gx = 0, saved_gy = 0; if(UWindowManager::RestoreWindowPosition(title, saved_gx, saved_gy)) { UState::SetInt(win_id + 1, saved_gx); UState::SetInt(win_id + 2, saved_gy); } else { UState::SetInt(win_id + 1, default_x); UState::SetInt(win_id + 2, default_y); } } int x = (int)UState::GetInt(win_id + 1, default_x); int y = (int)UState::GetInt(win_id + 2, default_y); int w = (int)UState::GetInt(win_id + 3, default_w); int h = (int)UState::GetInt(win_id + 4, default_h); bool collapsed = (UState::GetInt(win_id + 5, 0) != 0); int title_h = ((flags & U_WINDOW_NO_TITLEBAR) != 0) ? 0 : th.window_title_height; int cur_h = collapsed ? title_h : h; // 1. Process Collapse button in titlebar FIRST to prevent drag collision bool mouse_on_col_btn = false; if(title_h > 0 && (flags & U_WINDOW_NO_COLLAPSE) == 0) { ulong col_id = win_id + 20; int btn_size = title_h - 6; int btn_x = x + w - btn_size - 6; int btn_y = y + 3; mouse_on_col_btn = UMath::PointInRect(UInputState::MouseX(), UInputState::MouseY(), btn_x, btn_y, btn_size, btn_size); bool c_hov = false, c_clk = false, c_act = false; UWidget::Behavior(col_id, btn_x, btn_y, btn_size, btn_size, c_hov, c_clk, c_act); if(c_clk) { collapsed = !collapsed; UState::SetInt(win_id + 5, collapsed ? 1 : 0); cur_h = collapsed ? title_h : h; } } // 2. Active Dragging Engine (Absolute Anchor Drag) if(s_dragging_win_id == win_id) { if(UInputState::IsMouseDown()) { int dx = UInputState::MouseX() - s_anchor_mouse_x; int dy = UInputState::MouseY() - s_anchor_mouse_y; int new_x = s_anchor_win_x + dx; int new_y = s_anchor_win_y + dy; // Clamp to chart boundaries int chart_w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); int chart_h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); x = UMath::Clamp(new_x, 0, MathMax(0, chart_w - 40)); y = UMath::Clamp(new_y, 0, MathMax(0, chart_h - title_h)); UState::SetInt(win_id + 1, x); UState::SetInt(win_id + 2, y); #ifdef UI_BACKEND_CANVAS URendererCanvas::SetWindowPosition(win_id, x, y); #endif } else { // Drag ended on mouse release s_dragging_win_id = 0; UInputState::SetDragOwner(0); UInputState::EnableChartScroll(); UWindowManager::SaveWindowPosition(title, x, y); } } else if((flags & U_WINDOW_NO_MOVE) == 0 && s_dragging_win_id == 0 && UInputState::GetDragOwner() == 0) { // 3. Initiate Drag Detection on Mouse Click anywhere over widget/hub bool mouse_in_window = UMath::PointInRect(UInputState::MouseX(), UInputState::MouseY(), x, y, w, cur_h); if(UInputState::IsMouseClicked() && mouse_in_window && !mouse_on_col_btn) { s_candidate_drag_win_id = win_id; s_anchor_mouse_x = UInputState::MouseX(); s_anchor_mouse_y = UInputState::MouseY(); s_anchor_win_x = x; s_anchor_win_y = y; } } out_x = x; out_y = y; out_w = w; out_h = cur_h; out_collapsed = collapsed; // Initialize layout within window boundary ULayout::BeginWindow(x, y, w, h, title_h); return(!collapsed); } /// Convenient Begin overload when layout coordinates are not requested by caller. static bool Begin(const string title, bool &io_open, const int default_x, const int default_y, const int default_w, const int default_h, const uint flags = U_WINDOW_NONE) { int ox, oy, ow, oh; bool oc; return(Begin(title, io_open, default_x, default_y, default_w, default_h, flags, ox, oy, ow, oh, oc)); } /// Ends window scope and resolves candidate window drag. static void End(void) { // If this window was candidate for drag, check if any interactive child widget consumed the click if(s_candidate_drag_win_id == s_current_win_id && s_current_win_id != 0) { ulong active = UInputState::GetActive(); if(active == 0 || active == s_current_win_id) { s_dragging_win_id = s_candidate_drag_win_id; UInputState::SetDragOwner(s_dragging_win_id); UInputState::SetActive(s_dragging_win_id); UInputState::DisableChartScroll(); #ifdef UI_BACKEND_CANVAS URendererCanvas::BringWindowToFront(s_dragging_win_id); #endif } s_candidate_drag_win_id = 0; } s_current_win_id = 0; s_current_title = ""; UIdStack::Pop(); } /// Resets window scope cleanly if caller skipped End() or window collapsed. static void ResetScope(void) { if(s_current_win_id != 0) { s_current_win_id = 0; s_candidate_drag_win_id = 0; s_current_title = ""; UIdStack::Reset(); } } }; ulong UWindow::s_current_win_id = 0; ulong UWindow::s_dragging_win_id = 0; ulong UWindow::s_candidate_drag_win_id = 0; int UWindow::s_anchor_mouse_x = 0; int UWindow::s_anchor_mouse_y = 0; int UWindow::s_anchor_win_x = 0; int UWindow::s_anchor_win_y = 0; string UWindow::s_current_title = "";