//+------------------------------------------------------------------+ //| 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