//+------------------------------------------------------------------+ //| ZoneLevel.mqh | //| Copyright 2025, MetaQuotes Ltd. | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include #include "../Utils/TimePrice.mqh" class VisualSystem; // Forward declaration //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class ZoneLevel { protected: long chartId; string name; string levelText; string levelObjName; string levelTextObjName; string levelExtText; string levelExtObjName; string levelExtTextObjName; string suffix; color levelColor; color textColor; color mutedColor; int width, pxDiff; TimePrice a; TimePrice b; TimePrice c; TimePrice d; bool isBull; bool isDrawExt; // --- Dirty tracking --- bool m_dirty; // true if level needs redraw bool m_lastVisible; // previous visibility state bool m_lastExtVisible; // previous visibility state datetime m_from_time, m_to_time; double m_min_price, m_max_price; public: ZoneLevel() : m_dirty(true), m_lastVisible(false), m_lastExtVisible(false) {} ZoneLevel(string _name, string text = "", string _levelExtText = "", int _chartId = 0, color clr = clrWhite, int _width = 1); ~ZoneLevel() { Delete(); } // Core methods virtual bool IsVisible(datetime fromTime, datetime toTime, double minPrice, double maxPrice) { m_from_time = fromTime; m_to_time = toTime; m_min_price = minPrice; m_max_price = maxPrice; return false; } virtual bool IsExtVisible(datetime fromTime, datetime toTime, double minPrice, double maxPrice); virtual void Draw(long chart_id, VisualSystem* vs) {} virtual void DrawExt(long chart_id, VisualSystem* vs); // --- Dirty-aware render --- void UpdateDraw(VisualSystem* vs, datetime fromTime, datetime toTime, double minPrice, double maxPrice); // Mark dirty when any property changes void MarkDirty() { m_dirty = true; } // Property setters (call MarkDirty when changed) void SetText(string txt) { if(levelText != txt) { levelText = txt; MarkDirty(); } } void SetExtText(string txt) { if(levelExtText != txt) { levelExtText = txt; MarkDirty(); } } void SetColor(color clr) { if(levelColor != clr) { levelColor = clr; MarkDirty(); } } void SetBothColor(color clr) { if(levelColor != clr) { levelColor = clr; textColor = clr; MarkDirty(); } } void SetExtEnd(TimePrice &_a) {c = _a; MarkDirty();} void SetBull(bool _isBull, TimePrice &_a); void SetDrawExt(bool _isDrawExt); void SetFontSize(int fontSize); void SetAtBack(bool back = true); void SetFill(bool fill = true); // Getter string GetName() const { return name; } // Object deletion void Delete(); void DeleteText(); void DeleteExtender(); void DeleteExtenderText(); void DeleteLevel(); void DeleteLevelText(); datetime GetFromTime() const { return m_from_time; } // store as member datetime GetToTime() const { return m_to_time; } double GetMinPrice() const { return m_min_price; } double GetMaxPrice() const { return m_max_price; } protected: void DrawText(string name, string text, TimePrice &_a, double angle = 0); //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double RadToDeg(double rad) { return rad * 180.0 / M_PI; }//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CalcLineAngleDeg(datetime t1, double p1, datetime t2, double p2, double &angleDeg) { int x1, y1, x2, y2; if(!ChartTimePriceToXY(chartId, 0, t1, p1, x1, y1)) return false; if(!ChartTimePriceToXY(chartId, 0, t2, p2, x2, y2)) return false; double dx = (double)(x2 - x1); double dy = (double)(y1 - y2); if(MathAbs(dx) < 0.000001 && MathAbs(dy) < 0.000001) { angleDeg = 0.0; return true; } angleDeg = RadToDeg(MathArctan2(dy, dx)); return true; } }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ ZoneLevel::ZoneLevel(string _name, string text = "", string _levelExtText = "", int _chartId = 0, color clr = clrWhite, int _width = 1) : name(_name), levelText(text), levelExtText(_levelExtText), width(_width), chartId(_chartId), levelColor(clr), textColor(clr), isBull(false), isDrawExt(false), pxDiff(6), m_dirty(true), m_lastVisible(false), m_lastExtVisible(false) { suffix = "VIS_"; levelObjName = suffix + _name; levelTextObjName = suffix + "txt_" + _name; levelExtTextObjName = suffix + "txt_ext_" + _name; levelExtObjName = suffix + "ext_" + _name; } //+------------------------------------------------------------------+ //| Dirty-aware update – called by VisualSystem::Render() | //+------------------------------------------------------------------+ void ZoneLevel::UpdateDraw(VisualSystem* vs, datetime fromTime, datetime toTime, double minPrice, double maxPrice) { // 1. Check visibility for main level bool isVisible = IsVisible(fromTime, toTime, minPrice, maxPrice); bool visibilityChanged = (isVisible != m_lastVisible); m_lastVisible = isVisible; // 2. Check visibility for extender bool isExtVisible = IsExtVisible(fromTime, toTime, minPrice, maxPrice); bool visibilityExtChanged = (isExtVisible != m_lastExtVisible); m_lastExtVisible = isExtVisible; // 3. Handle MAIN level if(!isVisible) { if(visibilityChanged) DeleteLevel(); // main left visible area → delete objects } else { // main is visible – redraw if dirty OR just became visible if(visibilityChanged || m_dirty) { Draw(chartId, vs); } } // 4. Handle EXTENDER level (same dirty flag) if(!isExtVisible) { if(visibilityExtChanged) DeleteExtender(); // ext left visible area → delete objects } else { // ext is visible – redraw if dirty OR just became visible if(visibilityExtChanged || m_dirty) { DrawExt(chartId, vs); } } // 5. Clear the shared dirty flag only after both components have been processed if(m_dirty) m_dirty = false; } //+------------------------------------------------------------------+ //| Set Bull direction (moves text anchor) | //+------------------------------------------------------------------+ void ZoneLevel::SetBull(bool _isBull, TimePrice &_a) { if(isBull == _isBull && a.x == _a.x && a.y == _a.y) return; // no change isBull = _isBull; a = _a; MarkDirty(); // Convert original to screen X,Y, adjust, then update object position int x = 0, y = 0; ChartTimePriceToXY(chartId, 0, a.x, a.y, x, y); if(!isBull) y += pxDiff; else y -= pxDiff; int sub_window = 0; datetime newTime = 0; double newPrice = 0.0; ChartXYToTimePrice(chartId, x, y, sub_window, newTime, newPrice); ObjectSetDouble(chartId, name, OBJPROP_PRICE, newPrice); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool ZoneLevel:: IsExtVisible(datetime fromTime, datetime toTime, double minPrice, double maxPrice) { // Print("[PointLevel]::IsVisible() ",fromTime," :: ", a.x,"-- ", a.x >= fromTime); return (d.x >= fromTime && c.x <= toTime && d.y >= minPrice && c.y <= maxPrice); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ZoneLevel::SetDrawExt(bool _isDrawExt) { isDrawExt = _isDrawExt; MarkDirty(); } //+------------------------------------------------------------------+ //| Set font size for both text objects | //+------------------------------------------------------------------+ void ZoneLevel::SetFontSize(int fontSize) { ObjectSetInteger(chartId, levelTextObjName, OBJPROP_FONTSIZE, fontSize); ObjectSetInteger(chartId, levelExtTextObjName, OBJPROP_FONTSIZE, fontSize); // Font change doesn't require redraw of geometry, but we mark dirty anyway MarkDirty(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ZoneLevel::SetAtBack(bool back = true) { ObjectSetInteger(chartId, name, OBJPROP_BACK, back); MarkDirty(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ZoneLevel::SetFill(bool fill = true) { ObjectSetInteger(chartId, name, OBJPROP_FILL, fill); MarkDirty(); } //+------------------------------------------------------------------+ //| Draw text helper | //+------------------------------------------------------------------+ void ZoneLevel::DrawText(string name, string text, TimePrice &_a, double angle = 0) { int x = 0, y = 0; ChartTimePriceToXY(chartId, 0, _a.x, _a.y, x, y); if(!isBull) y += pxDiff; else y -= pxDiff; int sub_window = 0; datetime newTime = 0; double newPrice = 0.0; ChartXYToTimePrice(chartId, x, y, sub_window, newTime, newPrice); ObjectCreate(chartId, name, OBJ_TEXT, 0, _a.x, newPrice); ObjectSetString(chartId, name, OBJPROP_TEXT, text); ObjectSetInteger(chartId, name, OBJPROP_COLOR, textColor); ObjectSetDouble(chartId, name, OBJPROP_ANGLE, angle); if(!isBull) ObjectSetInteger(chartId, name, OBJPROP_ANCHOR, ANCHOR_UPPER); else ObjectSetInteger(chartId, name, OBJPROP_ANCHOR, ANCHOR_LOWER); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ZoneLevel::DrawExt(long chart_id, VisualSystem* vs) { if(a.Ok() && b.Ok()) d = b; else if(a.Ok() && !b.Ok()) { d = a; } if(ObjectFind(chartId, levelExtObjName) < 0) { ObjectCreate(chartId, levelExtObjName, OBJ_TREND, 0, d.x, d.y, c.x, c.y); } ObjectSetInteger(chartId, levelExtObjName, OBJPROP_WIDTH, 1); ObjectSetInteger(chartId, levelExtObjName, OBJPROP_COLOR, levelColor); ObjectSetInteger(chartId, levelExtObjName, OBJPROP_STYLE, STYLE_DOT); ObjectSetInteger(chartId, levelExtObjName, OBJPROP_BACK, false); if(levelExtText != "" && vs.Zoom() >= 2) { double angle; CalcLineAngleDeg(d.x, d.y, c.x, c.y, angle); DrawText(levelExtTextObjName, levelExtText, d.CenterCoord(c), angle); // SetFontSize(vs.GetScaledFontSize(12)); } } //+------------------------------------------------------------------+ //| Delete all objects | //+------------------------------------------------------------------+ void ZoneLevel::Delete() { // Print("Delete"); DeleteExtender(); DeleteLevel(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ZoneLevel::DeleteText() { DeleteLevelText(); DeleteExtenderText(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ZoneLevel::DeleteExtender() { DeleteExtenderText(); ObjectDelete(chartId, levelExtObjName); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ZoneLevel::DeleteExtenderText() { ObjectDelete(chartId, levelExtTextObjName); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ZoneLevel::DeleteLevel() { ObjectDelete(chartId, levelObjName); DeleteLevelText(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ZoneLevel::DeleteLevelText() { ObjectDelete(chartId, levelTextObjName); } //+------------------------------------------------------------------+