Article-22495-Dispatch-Driv.../AI Canvas Shell.mqh

327 lines
11 KiB
MQL5
Raw Permalink Normal View History

//+------------------------------------------------------------------+
//| AI Canvas Shell.mqh |
//| Copyright 2026, Allan Munene Mutiiria. |
//| https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Allan Munene Mutiiria."
#property link "https://t.me/Forex_Algo_Trader"
//--- Include guard
#ifndef AI_CANVAS_SHELL_MQH
#define AI_CANVAS_SHELL_MQH
//--- Include required modules
#include "AI Canvas State.mqh"
#include "AI Canvas Primitives.mqh"
#include "AI Canvas Render.mqh"
#include "AI Canvas Editor.mqh"
#include "AI Canvas Interact.mqh"
#include "AI Logic.mqh"
//--- Forward declare main EA inputs only when not compiled from main
#ifndef AI_COMPILED_FROM_MAIN
extern int MagicNumber; // Magic number for trade ops
extern bool EnableAutoSignal; // Enable auto signal on new bar
#endif
//+------------------------------------------------------------------+
//| Configure prompt and search editors with current layout |
//+------------------------------------------------------------------+
void Ai_ConfigureEditor()
{
//--- Compute prompt content width from current pane rect
int pL, pT, pR, pB;
Ai_GetPromptPaneRect(pL, pT, pR, pB);
int contentW = (pR - pL) - 2 * AI_TEXT_PAD - 8;
//--- Initialize main prompt editor
g_ai_editor.Init("Arial", AI_FONT_BODY, AI_TEXT_PAD, AI_TEXT_PAD);
g_ai_editor.SetWrapWidth(contentW);
g_ai_editor.SetPlaceholder("Type your prompt here... (Enter for newline, Shift+Enter to send)");
//--- Initialize search popup editor
g_ai_searchEditor.Init("Arial", AI_FONT_BODY, 4, 4);
g_ai_searchEditor.SetWrapWidth(2000);
g_ai_searchEditor.SetPlaceholder("Search across chat titles and conversations...");
}
//+------------------------------------------------------------------+
//| Recompute layout and reposition prompt overlay |
//+------------------------------------------------------------------+
void Ai_RecomputeLayout()
{
//--- Bail when main canvas does not exist
if(!g_ai_canvMainExists) return;
//--- Reconfigure editors with current dimensions
Ai_ConfigureEditor();
//--- Reposition prompt overlay to match new sidebar width
if(g_ai_canvPromptExists)
{
int dpL, dpT, dpR, dpB;
Ai_GetPromptPaneRect(dpL, dpT, dpR, dpB);
ObjectSetInteger(0, AI_CANVAS_NAME_PROMPT, OBJPROP_XDISTANCE, AI_DASHBOARD_X + dpL);
ObjectSetInteger(0, AI_CANVAS_NAME_PROMPT, OBJPROP_YDISTANCE, AI_DASHBOARD_Y + dpT);
}
}
//+------------------------------------------------------------------+
//| Create main dashboard canvas and prompt overlay |
//+------------------------------------------------------------------+
bool Ai_CreateMainCanvas()
{
//--- Skip if already created
if(g_ai_canvMainExists) return true;
//--- Compute full dashboard size
const int dW = AI_SIDEBAR_W_EXPANDED + AI_MAIN_W;
const int dH = AI_HEADER_H + AI_PAD + AI_DISPLAY_H + AI_PAD + AI_PROMPT_H + AI_PAD + AI_FOOTER_H;
//--- Create main canvas as bitmap label
if(!g_ai_canvMain.CreateBitmapLabel(AI_CANVAS_NAME_MAIN,
AI_DASHBOARD_X, AI_DASHBOARD_Y,
dW, dH, COLOR_FORMAT_ARGB_NORMALIZE))
{
Print("Ai_CreateMainCanvas: CreateBitmapLabel failed: ", GetLastError());
return false;
}
//--- Configure main canvas object properties
ObjectSetInteger(0, AI_CANVAS_NAME_MAIN, OBJPROP_BACK, false);
ObjectSetInteger(0, AI_CANVAS_NAME_MAIN, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, AI_CANVAS_NAME_MAIN, OBJPROP_HIDDEN, true);
ObjectSetInteger(0, AI_CANVAS_NAME_MAIN, OBJPROP_ZORDER, 1000);
g_ai_canvMainExists = true;
//--- Compute prompt overlay screen position and size
int ppL, ppT, ppR, ppB;
Ai_GetPromptPaneRect(ppL, ppT, ppR, ppB);
const int promptW = ppR - ppL;
const int promptH = ppB - ppT;
const int promptScreenX = AI_DASHBOARD_X + ppL;
const int promptScreenY = AI_DASHBOARD_Y + ppT;
//--- Try create prompt overlay - non-fatal if it fails
if(g_ai_canvPrompt.CreateBitmapLabel(AI_CANVAS_NAME_PROMPT,
promptScreenX, promptScreenY,
promptW, promptH, COLOR_FORMAT_ARGB_NORMALIZE))
{
//--- Configure prompt overlay base properties
ObjectSetInteger(0, AI_CANVAS_NAME_PROMPT, OBJPROP_BACK, false);
ObjectSetInteger(0, AI_CANVAS_NAME_PROMPT, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, AI_CANVAS_NAME_PROMPT, OBJPROP_HIDDEN, true);
//--- Suppress default object-name tooltip
ObjectSetString(0, AI_CANVAS_NAME_PROMPT, OBJPROP_TOOLTIP, "\n");
//--- Always-visible flag - hide done via bitmap content not visibility
ObjectSetInteger(0, AI_CANVAS_NAME_PROMPT, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS);
//--- High zorder for click event routing (visual stacking is by creation order)
ObjectSetInteger(0, AI_CANVAS_NAME_PROMPT, OBJPROP_ZORDER, 1001);
g_ai_canvPromptExists = true;
}
else
{
//--- Log failure and fall back to main-only renders
Print("Ai_CreateMainCanvas: prompt overlay create failed (",
GetLastError(), ") - falling back to main-only renders.");
g_ai_canvPromptExists = false;
}
return true;
}
//+------------------------------------------------------------------+
//| Destroy main canvas and prompt overlay |
//+------------------------------------------------------------------+
void Ai_DestroyMainCanvas()
{
//--- Bail when nothing to destroy
if(!g_ai_canvMainExists) return;
//--- Destroy prompt overlay first
if(g_ai_canvPromptExists)
{
g_ai_canvPrompt.Destroy();
ObjectDelete(0, AI_CANVAS_NAME_PROMPT);
g_ai_canvPromptExists = false;
}
//--- Destroy main canvas
g_ai_canvMain.Destroy();
ObjectDelete(0, AI_CANVAS_NAME_MAIN);
g_ai_canvMainExists = false;
}
//+------------------------------------------------------------------+
//| Show dashboard - load images, create canvas, render |
//+------------------------------------------------------------------+
void Ai_ShowDashboard()
{
//--- Load images for icons and logos
if(!Ai_LoadImages()) Print("Ai_ShowDashboard: image load partial");
//--- Create canvases and bail on failure
if(!Ai_CreateMainCanvas())
{
Print("Ai_ShowDashboard: failed to create canvas");
return;
}
//--- Configure editors and restore prompt text
Ai_ConfigureEditor();
g_ai_editor.SetText(g_ai_currentPrompt);
//--- Enable mouse events on chart
ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
ChartSetInteger(0, CHART_EVENT_MOUSE_WHEEL, true);
ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
//--- Mark visible and trigger first render
g_ai_dashboardVisible = true;
Ai_RenderAll();
ChartRedraw();
}
//+------------------------------------------------------------------+
//| Hide dashboard - persist state and destroy canvas |
//+------------------------------------------------------------------+
void Ai_HideDashboard()
{
//--- Save current chat state to disk
AiUpdateCurrentHistory();
//--- Release keyboard override
Ai_EndKeyboardOverride();
//--- Restore default chart scroll behavior
ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
//--- Destroy canvases and mark hidden
Ai_DestroyMainCanvas();
g_ai_dashboardVisible = false;
ChartRedraw();
}
//+------------------------------------------------------------------+
//| Initialize EA - called from OnInit |
//+------------------------------------------------------------------+
bool Ai_Init()
{
//--- Apply default theme
Ai_ApplyTheme(false);
//--- Initialize all scroll states
AiScrollInit(g_ai_chatScroll);
AiScrollInit(g_ai_bigScroll);
AiScrollInit(g_ai_searchScroll);
//--- Configure trade helper magic number
g_ai_trade.SetExpertMagicNumber(MagicNumber);
//--- Open log file in append mode
g_ai_logFileHandle = FileOpen(LogFileName, FILE_READ | FILE_WRITE | FILE_TXT);
if(g_ai_logFileHandle == INVALID_HANDLE)
{
Print("Failed to open log file: ", GetLastError());
}
else
{
FileSeek(g_ai_logFileHandle, 0, SEEK_END);
}
//--- Load saved chats from disk and show dashboard
AiLoadChats();
Ai_ShowDashboard();
return true;
}
//+------------------------------------------------------------------+
//| Deinitialize EA - called from OnDeinit |
//+------------------------------------------------------------------+
void Ai_Deinit()
{
//--- Save current chat state and hide dashboard
AiUpdateCurrentHistory();
Ai_HideDashboard();
//--- Close log file
if(g_ai_logFileHandle != INVALID_HANDLE)
{
FileClose(g_ai_logFileHandle);
g_ai_logFileHandle = INVALID_HANDLE;
}
//--- Destroy persistent chat scratch canvas
if(g_ai_canvChatTmpReady)
{
g_ai_canvChatTmp.Destroy();
g_ai_canvChatTmpReady = false;
}
//--- Destroy persistent search scratch canvas
if(g_ai_canvSearchTmpReady)
{
g_ai_canvSearchTmp.Destroy();
g_ai_canvSearchTmpReady = false;
}
//--- Destroy primitives high-res fill canvas
if(g_ai_prim.m_hrFillReady)
{
g_ai_prim.m_hrFill.Destroy();
g_ai_prim.m_hrFillReady = false;
}
//--- Destroy primitives high-res border canvas
if(g_ai_prim.m_hrBorderReady)
{
g_ai_prim.m_hrBorder.Destroy();
g_ai_prim.m_hrBorderReady = false;
}
//--- Destroy main editor scratch canvas
if(g_ai_editor.tmpCanvasReady)
{
g_ai_editor.tmpCanvas.Destroy();
g_ai_editor.tmpCanvasReady = false;
}
//--- Destroy search editor scratch canvas
if(g_ai_searchEditor.tmpCanvasReady)
{
g_ai_searchEditor.tmpCanvas.Destroy();
g_ai_searchEditor.tmpCanvasReady = false;
}
//--- Free image pixel buffers
ArrayResize(g_ai_pixHeader, 0);
ArrayResize(g_ai_pixSidebarBig, 0);
ArrayResize(g_ai_pixSidebarSmall, 0);
ArrayResize(g_ai_pixNewChat, 0);
ArrayResize(g_ai_pixClear, 0);
ArrayResize(g_ai_pixHistory, 0);
g_ai_imagesLoaded = false;
}
//+------------------------------------------------------------------+
//| Process new bar tick for auto signal generation |
//+------------------------------------------------------------------+
void Ai_OnTick()
{
//--- Read latest bar timestamp
datetime currentBarTime[1];
if(CopyTime(Symbol(), Period(), 0, 1, currentBarTime) != 1) return;
//--- Trigger auto signal on new bar when enabled
if(currentBarTime[0] != g_ai_lastBarTime && EnableAutoSignal)
{
g_ai_lastBarTime = currentBarTime[0];
Print("New bar detected at ", TimeToString(g_ai_lastBarTime, TIME_DATE | TIME_MINUTES),
". Running auto signal check...");
AiGetTradeSignal(true);
}
}
#endif // AI_CANVAS_SHELL_MQH