mql5/Experts/Motivated/Graphics/VisualSystem.mqh
younglubah-gmail 9639b8b362
2026-06-23 21:44:37 +02:00

360 lines
12 KiB
MQL5

//+------------------------------------------------------------------+
//| VisualSystem.mqh |
//| Copyright 2025, MetaQuotes Ltd. |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#property version "1.00"
#include "VisualInterfaces.mqh"
#include "ZoneLevel.mqh"
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
class VisualSystem : public ViewEvents
{
private:
long m_chart_id;
int m_width, m_height;
long m_zoom;
datetime m_from_time, m_to_time;
double m_min_price, m_max_price;
bool m_auto_update;
long m_min_zoom_to_keep_texts;
bool m_min_zoom_reached_already;
// Manual dynamic array
ZoneLevel* m_levels[];
int m_count;
int m_capacity;
void ResizeArray(int newCapacity);
public:
VisualSystem();
~VisualSystem();
void SetMinZoomForTexts(long zoomLevel) { m_min_zoom_to_keep_texts = zoomLevel; }
void SetAutoUpdate(bool enable) { m_auto_update = enable; }
long Zoom() const { return m_zoom; }
int GetScaledFontSize(int baseSizePoints) const;
// Level management
void AddLevel(ZoneLevel *level);
bool RemoveLevel(int index);
bool RemoveLevel(ZoneLevel *level);
ZoneLevel* GetLevel(int index) const;
ZoneLevel* GetLevelByName(string levelName);
int GetSize() const { return m_count; }
int GetCapacity() const { return m_capacity; }
void SetCapacity(int newCapacity);
void ClearLevels();
void DeleteLevel(int index);
// Rendering – now uses dirty tracking
void Render();
// Event handlers
void UpdateViewBounds();
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
// Virtual overrides
virtual void OnViewChanged() override;
virtual void OnZoomChanged(long newZoom, long oldZoom) override;
virtual void OnMinZoomReached() override { }
virtual void OnBeforeRender() override { UpdateViewBounds(); }
virtual void OnAfterRender() override { }
private:
void DeleteAllTextObjects();
// DeleteAllVisualObjects no longer used in Render, kept for emergency
void DeleteAllVisualObjects();
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
VisualSystem::VisualSystem()
: m_chart_id(ChartID()), m_width(0), m_height(0), m_zoom(0),
m_auto_update(true), m_min_zoom_to_keep_texts(2), m_min_zoom_reached_already(false),
m_count(0), m_capacity(0)
{
UpdateViewBounds();
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
VisualSystem::~VisualSystem()
{
ClearLevels();
}
//+------------------------------------------------------------------+
//| Array management |
//+------------------------------------------------------------------+
void VisualSystem::ResizeArray(int newCapacity)
{
if(newCapacity <= 0)
{
ArrayResize(m_levels, 0);
m_capacity = 0;
return;
}
ArrayResize(m_levels, newCapacity);
m_capacity = newCapacity;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void VisualSystem::SetCapacity(int newCapacity)
{
if(newCapacity <= m_count)
return;
ResizeArray(newCapacity);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void VisualSystem::AddLevel(ZoneLevel *level)
{
if(CheckPointer(level) == POINTER_INVALID)
return;
if(m_count >= m_capacity)
{
int newCap = (m_capacity == 0) ? 4 : m_capacity * 2;
ResizeArray(newCap);
}
m_levels[m_count] = level;
m_count++;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool VisualSystem::RemoveLevel(int index)
{
if(index < 0 || index >= m_count)
return false;
for(int i = index; i < m_count - 1; i++)
m_levels[i] = m_levels[i + 1];
m_count--;
if(m_count > 0)
m_levels[m_count] = NULL;
return true;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool VisualSystem::RemoveLevel(ZoneLevel *level)
{
if(level == NULL)
return false;
for(int i = 0; i < m_count; i++)
if(m_levels[i] == level)
return RemoveLevel(i);
return false;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
ZoneLevel* VisualSystem::GetLevelByName(string levelName)
{
for(int i = 0; i < m_count; i++)
if(m_levels[i].GetName() == levelName)
return m_levels[i];
return NULL;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void VisualSystem::DeleteLevel(int index)
{
if(index < 0 || index >= m_count)
return;
ZoneLevel* p = m_levels[index];
if(CheckPointer(p) != POINTER_INVALID)
delete p;
RemoveLevel(index);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
ZoneLevel* VisualSystem::GetLevel(int index) const
{
if(index < 0 || index >= m_count)
return NULL;
return m_levels[index];
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void VisualSystem::ClearLevels()
{
for(int i = 0; i < m_count; i++)
{
if(CheckPointer(m_levels[i]) != POINTER_INVALID)
delete m_levels[i];
m_levels[i] = NULL;
}
m_count = 0;
}
//+------------------------------------------------------------------+
//| Optimized Render – uses dirty flags |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Optimized Render – reverse iteration + early culling |
//+------------------------------------------------------------------+
void VisualSystem::Render()
{
OnBeforeRender(); // <-- updates m_from_time, m_to_time, m_min_price, m_max_price
// If bounds are invalid (chart not ready), just update all levels without culling
bool boundsValid = (m_from_time < m_to_time && m_min_price < m_max_price);
for(int i = m_count - 1; i >= 0; i--) // reverse iteration: draw newest first
{
ZoneLevel* p = m_levels[i];
if(p == NULL)
continue;
// Fast culling using level's stored time/price range
/* datetime levelFrom = p.GetFromTime();
datetime levelTo = p.GetToTime();
double levelMin = p.GetMinPrice();
double levelMax = p.GetMaxPrice();
// Skip if completely outside visible time OR price range
if(levelTo < m_from_time || levelFrom > m_to_time ||
levelMax < m_min_price || levelMin > m_max_price)
{
// Level not visible at all – skip UpdateDraw entirely
// (Objects will be deleted when they become invisible inside UpdateDraw later)
continue;
}
//} if(boundsValid)
//{*/
// Level intersects view (or bounds invalid) – update its drawing
p.UpdateDraw(GetPointer(this), m_from_time, m_to_time, m_min_price, m_max_price);
}
OnAfterRender();
}
//+------------------------------------------------------------------+
//| View bounds update |
//+------------------------------------------------------------------+
void VisualSystem::UpdateViewBounds()
{
long old_zoom = m_zoom;
datetime old_from = m_from_time, old_to = m_to_time;
double old_minp = m_min_price, old_maxp = m_max_price;
m_width = (int)ChartGetInteger(m_chart_id, CHART_WIDTH_IN_PIXELS);
m_height = (int)ChartGetInteger(m_chart_id, CHART_HEIGHT_IN_PIXELS);
m_zoom = ChartGetInteger(m_chart_id, CHART_SCALE);
int sub_window = 0;
ChartXYToTimePrice(m_chart_id, 0, 0, sub_window, m_from_time, m_max_price);
ChartXYToTimePrice(m_chart_id, m_width, m_height, sub_window, m_to_time, m_min_price);
if(m_zoom != old_zoom)
{
OnZoomChanged(m_zoom, old_zoom);
bool below_threshold = (m_zoom < m_min_zoom_to_keep_texts);
if(below_threshold && !m_min_zoom_reached_already)
{
DeleteAllTextObjects();
OnMinZoomReached();
m_min_zoom_reached_already = true;
}
else
if(!below_threshold)
{
m_min_zoom_reached_already = false;
}
}
if(m_from_time != old_from || m_to_time != old_to ||
m_min_price != old_minp || m_max_price != old_maxp)
{
OnViewChanged();
}
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
int VisualSystem::GetScaledFontSize(int baseSizePoints) const
{
return (int)MathMax(6.0, baseSizePoints - (m_zoom - 2));
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void VisualSystem::DeleteAllTextObjects()
{
for(int i = 0; i < m_count; i++)
if(CheckPointer(m_levels[i]) != POINTER_INVALID)
m_levels[i].DeleteText();
}
// Emergency cleanup – not used in normal render but kept for completeness
void VisualSystem::DeleteAllVisualObjects()
{
for(int i = 0; i < m_count; i++)
if(CheckPointer(m_levels[i]) != POINTER_INVALID)
m_levels[i].Delete();
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void VisualSystem::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
if(id == CHARTEVENT_CHART_CHANGE)
UpdateViewBounds();
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void VisualSystem::OnViewChanged(void)
{
Render();
// Print("OnViewChanged");
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void VisualSystem::OnZoomChanged(long newZoom, long oldZoom)
{
if(!m_min_zoom_reached_already)
for(int i = 0; i < m_count; i++)
if(CheckPointer(m_levels[i]) != POINTER_INVALID)
m_levels[i].SetFontSize(GetScaledFontSize(12));
}
//+------------------------------------------------------------------+