//+------------------------------------------------------------------+ //| News Render.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 NEWS_RENDER_MQH #define NEWS_RENDER_MQH //--- Include core data definitions and state #include "News Core.mqh" //--- Include event loading and filter logic #include "News Logic.mqh" //+------------------------------------------------------------------+ //| Global Canvas Instances | //+------------------------------------------------------------------+ CCanvas g_news_canv; // Main dashboard canvas CCanvas g_news_canvSep; // Separator lines overlay canvas (separate chart object) bool g_news_canvSepExists = false; // Separator canvas created flag #define NEWS_CANVAS_NAME_SEP "NewsCanvasSeparators" // Separator canvas object name //+------------------------------------------------------------------+ //| Header Button Rectangle Caches | //+------------------------------------------------------------------+ int g_news_closeL = 0, g_news_closeT = 0, g_news_closeR = 0, g_news_closeB = 0; // Close button bounds int g_news_themeL = 0, g_news_themeT = 0, g_news_themeR = 0, g_news_themeB = 0; // Theme button bounds //+------------------------------------------------------------------+ //| Filter Toggle Rectangle Caches | //+------------------------------------------------------------------+ int g_news_currTglL = 0, g_news_currTglT = 0, g_news_currTglR = 0, g_news_currTglB = 0; // Currency toggle bounds int g_news_impTglL = 0, g_news_impTglT = 0, g_news_impTglR = 0, g_news_impTglB = 0; // Impact toggle bounds int g_news_timeTglL = 0, g_news_timeTglT = 0, g_news_timeTglR = 0, g_news_timeTglB = 0; // Time toggle bounds //+------------------------------------------------------------------+ //| Currency Chip Rectangle Caches | //+------------------------------------------------------------------+ int g_news_currL[NEWS_CURR_COUNT]; // Currency chip left bounds int g_news_currT[NEWS_CURR_COUNT]; // Currency chip top bounds int g_news_currR[NEWS_CURR_COUNT]; // Currency chip right bounds int g_news_currB[NEWS_CURR_COUNT]; // Currency chip bottom bounds //+------------------------------------------------------------------+ //| Impact Chip Rectangle Caches | //+------------------------------------------------------------------+ int g_news_impL[NEWS_IMPACT_COUNT]; // Impact chip left bounds int g_news_impT[NEWS_IMPACT_COUNT]; // Impact chip top bounds int g_news_impR[NEWS_IMPACT_COUNT]; // Impact chip right bounds int g_news_impB[NEWS_IMPACT_COUNT]; // Impact chip bottom bounds //+------------------------------------------------------------------+ //| Event Row Rectangle Caches | //+------------------------------------------------------------------+ //--- Sized for max dashboard height (~900px yields about 25 rows); keep headroom #define NEWS_MAX_VISIBLE_ROWS 32 int g_news_rowL[NEWS_MAX_VISIBLE_ROWS]; // Row left bounds int g_news_rowT[NEWS_MAX_VISIBLE_ROWS]; // Row top bounds int g_news_rowR[NEWS_MAX_VISIBLE_ROWS]; // Row right bounds int g_news_rowB[NEWS_MAX_VISIBLE_ROWS]; // Row bottom bounds int g_news_rowEventIdx[NEWS_MAX_VISIBLE_ROWS]; // Row index into displayableEvents int g_news_visibleRowCount = 0; // Count of currently visible rows //+------------------------------------------------------------------+ //| Revised Triangle Hot Zone Caches | //+------------------------------------------------------------------+ //--- cx == -1 means the row has no revised marker; interact layer sets the MT5 tooltip int g_news_revTriCx[NEWS_MAX_VISIBLE_ROWS]; // Triangle center X per row (-1 if none) int g_news_revTriCy[NEWS_MAX_VISIBLE_ROWS]; // Triangle center Y per row int g_news_revisedHoverRow = -1; // Visible row index currently hovered (-1 if none) //+------------------------------------------------------------------+ //| Cursor Position State | //+------------------------------------------------------------------+ //--- Updated by interact layer on every mouse move; used to float resize handles int g_news_cursorX = -1; // Last cursor X in dashboard-local coordinates int g_news_cursorY = -1; // Last cursor Y in dashboard-local coordinates //+------------------------------------------------------------------+ //| Remain Cell Fast-Path Cache | //+------------------------------------------------------------------+ //--- Lets the timer repaint only cells whose displayed string changed int g_news_remainCellL[NEWS_MAX_VISIBLE_ROWS]; // Remain cell left edge int g_news_remainCellT[NEWS_MAX_VISIBLE_ROWS]; // Remain cell top edge int g_news_remainCellW[NEWS_MAX_VISIBLE_ROWS]; // Remain cell width int g_news_remainCellH[NEWS_MAX_VISIBLE_ROWS]; // Remain cell height color g_news_remainCellBg[NEWS_MAX_VISIBLE_ROWS]; // Remain cell row background for repaint string g_news_remainLastStr[NEWS_MAX_VISIBLE_ROWS]; // Last displayed remain string datetime g_news_remainEvTime[NEWS_MAX_VISIBLE_ROWS]; // Event datetime driving recompute //+------------------------------------------------------------------+ //| HR Drawing Canvas Instances | //+------------------------------------------------------------------+ CCanvas g_news_hrFillCanv; // HR fill offscreen canvas bool g_news_hrFillReady = false; // HR fill canvas ready flag int g_news_hrFillW = 0; // HR fill canvas width int g_news_hrFillH = 0; // HR fill canvas height CCanvas g_news_hrBordCanv; // HR border offscreen canvas bool g_news_hrBordReady = false; // HR border canvas ready flag int g_news_hrBordW = 0; // HR border canvas width int g_news_hrBordH = 0; // HR border canvas height //+------------------------------------------------------------------+ //| Footer and Padding Constants | //+------------------------------------------------------------------+ #define NEWS_FOOTER_H 26 // Countdown banner height in pixels #define NEWS_FOOTER_PAD 8 // Gap between row area bottom and footer top //+------------------------------------------------------------------+ //| Compute table section vertical positions | //+------------------------------------------------------------------+ int News_TableTop() { //--- Sum all fixed-height bands above the table return NEWS_HEADER_H + NEWS_FILTER_H + NEWS_CURR_ROW_H + NEWS_IMPACT_ROW_H + NEWS_VERT_GAP; } //+------------------------------------------------------------------+ //| Return table header top Y coordinate | //+------------------------------------------------------------------+ int News_TableHeaderTop() { //--- Table header starts at the same Y as the table top return News_TableTop(); } //+------------------------------------------------------------------+ //| Return table rows viewport top Y coordinate | //+------------------------------------------------------------------+ int News_TableRowsTop() { //--- Rows start below the table column header return News_TableTop() + NEWS_TABLE_HDR_H; } //+------------------------------------------------------------------+ //| Return table rows viewport bottom Y coordinate | //+------------------------------------------------------------------+ int News_TableRowsBottom() { //--- Viewport extends to the footer top with no whole-row snap const int footerTop = NEWS_DASHBOARD_H - NEWS_FOOTER_H - NEWS_FOOTER_PAD; const int rowsTop = News_TableRowsTop(); //--- Guard against pathologically small dashboard heights if(footerTop <= rowsTop + 1) return rowsTop + NEWS_ROW_H; return footerTop; } //+------------------------------------------------------------------+ //| Return count of fully visible rows in the viewport | //+------------------------------------------------------------------+ int News_VisibleRowCount() { //--- Compute how many whole rows fit in the available viewport height const int rowsTop = News_TableRowsTop(); const int rowsBot = News_TableRowsBottom(); return MathMax(1, (rowsBot - rowsTop) / NEWS_ROW_H); } //+------------------------------------------------------------------+ //| Stamp normal-weight text onto the main canvas | //+------------------------------------------------------------------+ void News_StampTextAA_Wrapper(const string txt, int x, int y, string font, int size, color clr) { //--- Delegate to shared News_StampText helper News_StampText(g_news_canv, x, y, txt, font, size, clr); } //+------------------------------------------------------------------+ //| Render dashboard header with logo, title, server time, buttons | //+------------------------------------------------------------------+ void News_RenderHeader() { //--- Fill header strip with flat background rectangle g_news_canv.FillRectangle(0, 0, NEWS_DASHBOARD_W - 1, NEWS_HEADER_H - 1, ColorToARGB(g_news_headerBg, 255)); //--- Compute button dimensions anchored to the right edge const int btnH = NEWS_HEADER_H - 7; const int themeW = btnH; const int closeW = (int)(btnH * 1.5); //--- Set close button rect (rightmost, flush to dashboard right edge) g_news_closeR = NEWS_DASHBOARD_W; g_news_closeL = g_news_closeR - closeW; g_news_closeT = 0; g_news_closeB = g_news_closeT + btnH; //--- Set theme toggle rect (immediately left of close button) g_news_themeR = g_news_closeL; g_news_themeL = g_news_themeR - themeW; g_news_themeT = 0; g_news_themeB = btnH; //--- Compute title bounds and available width for truncation const string titleFull = "MQL5 Economic Calendar"; const int titleStartX = NEWS_SIDE_PAD; const int midGap = 12; const int rightAreaL = g_news_themeL - 4; const int titleMaxW = (rightAreaL - titleStartX) / 2 - midGap; const int titleH = News_TextHeight("Arial Bold", NEWS_FONT_TITLE); const int titleY = (NEWS_HEADER_H - titleH) / 2; //--- Stamp title with ellipsis fallback if space is tight const string titleFit = News_FitTextToWidth(titleFull, "Arial Bold", NEWS_FONT_TITLE, MathMax(0, titleMaxW)); News_StampTextAA_Wrapper(titleFit, titleStartX, titleY, "Arial Bold", NEWS_FONT_TITLE, g_news_titleText); const int titleEndX = titleStartX + News_TextWidth(titleFit, "Arial Bold", NEWS_FONT_TITLE); //--- Render middle status text (server time + event counts) when there is room const int midAvailL = titleEndX + midGap; const int midAvailR = rightAreaL - 4; const int midAvailW = midAvailR - midAvailL; if(midAvailW > 40) { //--- Build status string and center it in the available band string countsStr = "Total: " + IntegerToString(g_news_totalFiltered) + "/" + IntegerToString(g_news_totalConsidered); string serverStr = "Server: " + TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS); string mid = serverStr + " | " + countsStr; string midFit = News_FitTextToWidth(mid, "Arial", NEWS_FONT_HEADING, midAvailW); const int midW = News_TextWidth(midFit, "Arial", NEWS_FONT_HEADING); const int midX = midAvailL + (midAvailW - midW) / 2; const int midY = (NEWS_HEADER_H - News_TextHeight("Arial", NEWS_FONT_HEADING)) / 2; News_StampTextAA_Wrapper(midFit, midX, midY, "Arial", NEWS_FONT_HEADING, g_news_subText); } //--- Draw theme button hover fill if(g_news_hover == NEWS_HOV_THEME) { const color hovBg = News_HoverForBg(g_news_headerBg); g_news_canv.FillRectangle(g_news_themeL, g_news_themeT, g_news_themeR - 1, g_news_themeB - 1, ColorToARGB(hovBg, 255)); } //--- Stamp theme toggle glyph centered in its button area const string themeGlyph = "\x5B"; const int thW = News_TextWidth(themeGlyph, "Wingdings", 14); const int thH = News_TextHeight("Wingdings", 14); News_StampTextAA_Wrapper(themeGlyph, g_news_themeL + (themeW - thW) / 2, g_news_themeT + (btnH - thH) / 2, "Wingdings", 14, g_news_titleText); //--- Draw close button hover fill and set foreground color color cFg = (g_news_hover == NEWS_HOV_CLOSE) ? g_news_closeColorHover : g_news_closeColor; if(g_news_hover == NEWS_HOV_CLOSE) g_news_canv.FillRectangle(g_news_closeL, g_news_closeT, g_news_closeR - 1, g_news_closeB - 1, ColorToARGB(g_news_closeBgHover, 255)); //--- Stamp close glyph centered in its button area const int xW = News_TextWidth(NEWS_GLYPH_CLOSE, "Webdings", 14); const int xH = News_TextHeight("Webdings", 14); News_StampTextAA_Wrapper(NEWS_GLYPH_CLOSE, g_news_closeL + (closeW - xW) / 2, g_news_closeT + (btnH - xH) / 2, "Webdings", 14, cFg); //--- Draw header bottom border line g_news_canv.LineHorizontal(0, NEWS_DASHBOARD_W - 1, NEWS_HEADER_H - 1, ColorToARGB(g_news_border, 255)); } //+------------------------------------------------------------------+ //| Render three filter master toggle buttons | //+------------------------------------------------------------------+ void News_RenderFilterToggles() { const int rowY = NEWS_HEADER_H + 4; const int btnH = NEWS_FILTER_H - 8; const int gap = 6; const int btnMaxW = 110; const int btnMinW = 50; //--- Stamp "Filters:" label on the left const int lblW = News_TextWidth("Filters:", "Arial Bold", NEWS_FONT_HEADING); News_StampTextAA_Wrapper("Filters:", NEWS_SIDE_PAD, rowY + (btnH - News_TextHeight("Arial Bold", NEWS_FONT_HEADING)) / 2, "Arial Bold", NEWS_FONT_HEADING, g_news_subText); //--- Compute per-button width within available area, clamped to min/max const int areaL = NEWS_SIDE_PAD + lblW + 8; const int areaR = NEWS_DASHBOARD_W - NEWS_SIDE_PAD; const int areaW = areaR - areaL; int btnW = (areaW - 2 * gap) / 3; if(btnW > btnMaxW) btnW = btnMaxW; if(btnW < btnMinW) btnW = btnMinW; //--- Right-align the three-button cluster within the available area const int totalW = btnW * 3 + gap * 2; int startX = areaR - totalW; //--- Define per-button label sets and state flags const string nounsOn[] = {"ON Currency", "ON Impact", "ON Time"}; const string nounsOff[] = {"OFF Currency", "OFF Impact", "OFF Time"}; const string shortOn[] = {"Currency", "Impact", "Time"}; const string shortOff[] = {"Currency", "Impact", "Time"}; const bool onFlags[] = {g_news_filterCurrencyOn, g_news_filterImpactOn, g_news_filterTimeOn}; const int hovCodes[] = {NEWS_HOV_FILTER_CURR, NEWS_HOV_FILTER_IMP, NEWS_HOV_FILTER_TIME}; //--- Render each of the three toggle buttons for(int i = 0; i < 3; i++) { const int bL = startX; const int bT = rowY; const int bR = startX + btnW; const int bB = rowY + btnH; //--- Cache button rect for hit-tester if(i == 0) { g_news_currTglL = bL; g_news_currTglT = bT; g_news_currTglR = bR; g_news_currTglB = bB; } if(i == 1) { g_news_impTglL = bL; g_news_impTglT = bT; g_news_impTglR = bR; g_news_impTglB = bB; } if(i == 2) { g_news_timeTglL = bL; g_news_timeTglT = bT; g_news_timeTglR = bR; g_news_timeTglB = bB; } //--- Compute background and foreground colors based on on/hover state color bg = onFlags[i] ? g_news_chipOnBg : g_news_chipOffBg; color fg = onFlags[i] ? g_news_chipOnText : g_news_chipOffText; if(g_news_hover == hovCodes[i]) bg = News_HoverForBg(bg); //--- Draw button background and border News_FillRoundRect(g_news_canv, bL, bT, btnW, btnH, 6, ColorToARGB(bg, 255)); News_DrawRoundRectBorder(g_news_canv, bL, bT, btnW, btnH, 6, 1, ColorToARGB(News_BorderForBg(bg), 255)); //--- Select the best label that fits: full, short, then ellipsized const string lblFull = onFlags[i] ? nounsOn[i] : nounsOff[i]; const string lblShort = onFlags[i] ? shortOn[i] : shortOff[i]; const int innerPad = 8; const int avail = btnW - 2 * innerPad; string lbl; if(News_TextWidth(lblFull, "Arial Bold", NEWS_FONT_BUTTON) <= avail) lbl = lblFull; else if(News_TextWidth(lblShort, "Arial Bold", NEWS_FONT_BUTTON) <= avail) lbl = lblShort; else lbl = News_FitTextToWidth(lblShort, "Arial Bold", NEWS_FONT_BUTTON, avail); //--- Center label inside button const int lW = News_TextWidth(lbl, "Arial Bold", NEWS_FONT_BUTTON); const int lH = News_TextHeight("Arial Bold", NEWS_FONT_BUTTON); News_StampTextAA_Wrapper(lbl, bL + (btnW - lW) / 2, bT + (btnH - lH) / 2, "Arial Bold", NEWS_FONT_BUTTON, fg); startX += btnW + gap; } } //+------------------------------------------------------------------+ //| Render the 8 currency filter chips in a row | //+------------------------------------------------------------------+ void News_RenderCurrencyChips() { const int rowY = NEWS_HEADER_H + NEWS_FILTER_H + 4; const int chipH = NEWS_CURR_ROW_H - 8; const int chipMaxW = 70; const int chipMinW = 36; const int gap = 6; //--- Compute per-chip width within available area, clamped to min/max const int areaW = NEWS_DASHBOARD_W - 2 * NEWS_SIDE_PAD; int chipW = (areaW - gap * (NEWS_CURR_COUNT - 1)) / NEWS_CURR_COUNT; if(chipW > chipMaxW) chipW = chipMaxW; if(chipW < chipMinW) chipW = chipMinW; //--- Center the chip row within available dashboard width const int totalW = chipW * NEWS_CURR_COUNT + gap * (NEWS_CURR_COUNT - 1); int startX = (NEWS_DASHBOARD_W - totalW) / 2; //--- Render each currency chip for(int i = 0; i < NEWS_CURR_COUNT; i++) { //--- Cache chip rect for hit-tester g_news_currL[i] = startX; g_news_currT[i] = rowY; g_news_currR[i] = startX + chipW; g_news_currB[i] = rowY + chipH; //--- Compute chip colors based on selection and hover state const bool sel = g_news_currSelected[i]; const bool hov = (g_news_hover == NEWS_HOV_CURR_BASE + i); color bg = sel ? g_news_currOnBg : g_news_currOffBg; if(hov) bg = sel ? News_HoverForBg(bg) : g_news_chipOffBg; color fg = sel ? g_news_currOnText : g_news_currOffText; //--- Draw chip background and border News_FillRoundRect(g_news_canv, g_news_currL[i], g_news_currT[i], chipW, chipH, 6, ColorToARGB(bg, 255)); News_DrawRoundRectBorder(g_news_canv, g_news_currL[i], g_news_currT[i], chipW, chipH, 6, 1, ColorToARGB(News_BorderForBg(bg), 255)); //--- Truncate 3-letter code if chip is unusually narrow then center const string lblFit = News_FitTextToWidth(NEWS_CURRENCIES[i], "Arial Bold", NEWS_FONT_BUTTON, chipW - 8); const int lW = News_TextWidth(lblFit, "Arial Bold", NEWS_FONT_BUTTON); const int lH = News_TextHeight("Arial Bold", NEWS_FONT_BUTTON); News_StampTextAA_Wrapper(lblFit, g_news_currL[i] + (chipW - lW) / 2, g_news_currT[i] + (chipH - lH) / 2, "Arial Bold", NEWS_FONT_BUTTON, fg); startX += chipW + gap; } } //+------------------------------------------------------------------+ //| Render the 4 impact filter chips in a row | //+------------------------------------------------------------------+ void News_RenderImpactChips() { const int rowY = NEWS_HEADER_H + NEWS_FILTER_H + NEWS_CURR_ROW_H + 4; const int chipH = NEWS_IMPACT_ROW_H - 8; const int chipMaxW = 110; const int chipMinW = 60; const int gap = 6; //--- Compute per-chip width within available area, clamped to min/max const int areaW = NEWS_DASHBOARD_W - 2 * NEWS_SIDE_PAD; int chipW = (areaW - gap * (NEWS_IMPACT_COUNT - 1)) / NEWS_IMPACT_COUNT; if(chipW > chipMaxW) chipW = chipMaxW; if(chipW < chipMinW) chipW = chipMinW; //--- Center the chip row within available dashboard width const int totalW = chipW * NEWS_IMPACT_COUNT + gap * (NEWS_IMPACT_COUNT - 1); int startX = (NEWS_DASHBOARD_W - totalW) / 2; //--- Pre-populate base semantic colors for each impact level color baseColors[]; ArrayResize(baseColors, NEWS_IMPACT_COUNT); baseColors[0] = g_news_impNone; baseColors[1] = g_news_impLow; baseColors[2] = g_news_impMed; baseColors[3] = g_news_impHigh; //--- Render each impact chip for(int i = 0; i < NEWS_IMPACT_COUNT; i++) { //--- Cache chip rect for hit-tester g_news_impL[i] = startX; g_news_impT[i] = rowY; g_news_impR[i] = startX + chipW; g_news_impB[i] = rowY + chipH; //--- Compute chip colors based on selection and hover state const bool sel = g_news_impactSelected[i]; const bool hov = (g_news_hover == NEWS_HOV_IMP_BASE + i); color baseClr = baseColors[i]; color bg = sel ? baseClr : g_news_currOffBg; if(hov) bg = sel ? News_HoverForBg(bg) : g_news_chipOffBg; color fg = sel ? clrWhite : g_news_currOffText; if(i == 0 && sel) fg = clrWhite; //--- Draw chip background and border News_FillRoundRect(g_news_canv, g_news_impL[i], g_news_impT[i], chipW, chipH, 6, ColorToARGB(bg, 255)); News_DrawRoundRectBorder(g_news_canv, g_news_impL[i], g_news_impT[i], chipW, chipH, 6, 1, ColorToARGB(News_BorderForBg(bg), 255)); //--- Truncate label if chip is narrow then center const string lblFit = News_FitTextToWidth(NEWS_IMPACT_LABELS[i], "Arial Bold", NEWS_FONT_BUTTON, chipW - 8); const int lW = News_TextWidth(lblFit, "Arial Bold", NEWS_FONT_BUTTON); const int lH = News_TextHeight("Arial Bold", NEWS_FONT_BUTTON); News_StampTextAA_Wrapper(lblFit, g_news_impL[i] + (chipW - lW) / 2, g_news_impT[i] + (chipH - lH) / 2, "Arial Bold", NEWS_FONT_BUTTON, fg); startX += chipW + gap; } } //+------------------------------------------------------------------+ //| Render table column header strip | //+------------------------------------------------------------------+ void News_RenderTableHeader() { const int hT = News_TableHeaderTop(); const int hL = NEWS_SIDE_PAD; const int hR = NEWS_DASHBOARD_W - NEWS_SIDE_PAD; const int hW = hR - hL; const int hH = NEWS_TABLE_HDR_H; //--- Fill header with flat background; radius 0 aligns cleanly with rows below News_FillRoundRect(g_news_canv, hL, hT, hW, hH, 0, ColorToARGB(g_news_tableHdrBg, 255)); //--- Stamp each column label truncated to fit its allocated width int x = hL + 6; for(int i = 0; i < NEWS_COL_COUNT; i++) { const int colW = g_news_colW[i]; const int tH = News_TextHeight("Arial Bold", NEWS_FONT_HEADING); const int textY = hT + (hH - tH) / 2; string label = News_FitTextToWidth(NEWS_COL_LABELS[i], "Arial Bold", NEWS_FONT_HEADING, colW - 8); News_StampTextAA_Wrapper(label, x + 2, textY, "Arial Bold", NEWS_FONT_HEADING, g_news_tableHdrText); x += colW; } } //+------------------------------------------------------------------+ //| Render column separators onto the overlay canvas | //+------------------------------------------------------------------+ void News_RenderSeparatorsOverlay() { //--- Skip if separator canvas was not created if(!g_news_canvSepExists) return; //--- Clear overlay to fully transparent g_news_canvSep.Erase(0x00000000); const int hT = News_TableHeaderTop(); const int hH = NEWS_TABLE_HDR_H; //--- Inset separator lines slightly from header top and bottom edges const int sepTopHdr = hT + 6; const int sepBotHdr = hT + hH - 6; //--- Blend border and subText halfway for a readable but subtle separator color const uchar bR = (uchar)((g_news_border ) & 0xFF); const uchar bG = (uchar)((g_news_border >> 8) & 0xFF); const uchar bB = (uchar)((g_news_border >> 16) & 0xFF); const uchar sR = (uchar)((g_news_subText ) & 0xFF); const uchar sG = (uchar)((g_news_subText >> 8) & 0xFF); const uchar sB = (uchar)((g_news_subText >> 16) & 0xFF); const color sepCol = (color)((((uint)((bB + sB) / 2)) << 16) | (((uint)((bG + sG) / 2)) << 8) | ((uint)((bR + sR) / 2))); const uint sepArgb = ColorToARGB(sepCol, 255); //--- Draw 1px separator after each column except the last const int hL = NEWS_SIDE_PAD; int x = hL + 6; for(int i = 0; i < NEWS_COL_COUNT; i++) { const int colW = g_news_colW[i]; if(i < NEWS_COL_COUNT - 1) { const int sepX = x + colW - 1; g_news_canvSep.FillRectangle(sepX, sepTopHdr, sepX, sepBotHdr, sepArgb); } x += colW; } //--- Push separator overlay to chart g_news_canvSep.Update(); } //+------------------------------------------------------------------+ //| Render visible event and day-separator rows with clipping | //+------------------------------------------------------------------+ void News_RenderEventRows() { const int hL = NEWS_SIDE_PAD; const int rowsTop = News_TableRowsTop(); const int rowsBot = News_TableRowsBottom(); const int totalRows = ArraySize(g_news_rowPlan); //--- Determine whether the scrollbar will be shown and reserve right margin const int viewportH = rowsBot - rowsTop; const int contentH = totalRows * NEWS_ROW_H; const bool scrollNeeded = (contentH > viewportH); const int scrollReserve = scrollNeeded ? 12 : 0; const int hR = NEWS_DASHBOARD_W - NEWS_SIDE_PAD - scrollReserve; const int hW = hR - hL; //--- Fill the rows viewport region with the panel background News_FillRoundRect(g_news_canv, hL, rowsTop, hW, rowsBot - rowsTop, 0, ColorToARGB(g_news_panelAlt, 255)); //--- Render empty-state message when no events match the current filters if(totalRows == 0) { const string empty = "No events match the current filters."; const int eW = News_TextWidth(empty, "Arial", NEWS_FONT_BODY); const int eH = News_TextHeight("Arial", NEWS_FONT_BODY); News_StampTextAA_Wrapper(empty, hL + (hW - eW) / 2, rowsTop + ((rowsBot - rowsTop) - eH) / 2, "Arial", NEWS_FONT_BODY, g_news_subText); g_news_visibleRowCount = 0; g_news_tableScroll.totalH = 0; g_news_tableScroll.viewportH = rowsBot - rowsTop; News_ScrollClamp(g_news_tableScroll); return; } //--- Update scroll state dimensions and clamp offset g_news_tableScroll.totalH = totalRows * NEWS_ROW_H; g_news_tableScroll.viewportH = rowsBot - rowsTop; News_ScrollClamp(g_news_tableScroll); //--- Allocate or grow the persistent scratch canvas to main canvas dimensions const int cw = g_news_canv.Width(); const int ch = g_news_canv.Height(); if(!g_news_tableTmpReady || g_news_tableTmpW < cw || g_news_tableTmpH < ch) { if(g_news_tableTmpReady) g_news_tableTmp.Destroy(); const int newW = MathMax(cw, g_news_tableTmpW); const int newH = MathMax(ch, g_news_tableTmpH); if(!g_news_tableTmp.CreateBitmap("NewsTableTmpPersistent", 0, 0, newW, newH, COLOR_FORMAT_ARGB_NORMALIZE)) return; g_news_tableTmpW = newW; g_news_tableTmpH = newH; g_news_tableTmpReady = true; } //--- Define the clip rectangle bounding the rows viewport const int clipL = hL; const int clipT = rowsTop; const int clipR = NEWS_DASHBOARD_W - NEWS_SIDE_PAD; const int clipB = rowsBot; const int seedL = MathMax(clipL, 0); const int seedT = MathMax(clipT, 0); const int seedR = MathMin(clipR, cw); const int seedB = MathMin(clipB, ch); //--- Seed scratch canvas with current main pixels for the clip region g_news_tableTmp.CopyRectFromCanvas(g_news_canv, seedL, seedT, seedR, seedB); //--- Compute the first visible row index and its Y position const int firstIdx = MathMax(0, g_news_tableScroll.scrollPx / NEWS_ROW_H - 1); const int firstY = rowsTop + firstIdx * NEWS_ROW_H - g_news_tableScroll.scrollPx; const int maxRows = MathMin(News_VisibleRowCount() + 3, totalRows - firstIdx); g_news_visibleRowCount = 0; //--- Render each row in the visible window onto the scratch canvas for(int v = 0; v < maxRows; v++) { const int planIdx = firstIdx + v; if(planIdx >= totalRows) break; const int rT = firstY + v * NEWS_ROW_H; const int rB = rT + NEWS_ROW_H; //--- Skip rows that fall entirely outside the clip viewport if(rB <= rowsTop || rT >= rowsBot) continue; //--- Cache hit-test rect clamped to the visible viewport const int hitT = MathMax(rT, rowsTop); const int hitB = MathMin(rB, rowsBot); g_news_rowL[g_news_visibleRowCount] = hL; g_news_rowT[g_news_visibleRowCount] = hitT; g_news_rowR[g_news_visibleRowCount] = hR; g_news_rowB[g_news_visibleRowCount] = hitB; g_news_rowEventIdx[g_news_visibleRowCount] = planIdx; const NewsRowEntry entry = g_news_rowPlan[planIdx]; //--- Render day separator row if(entry.kind == NEWS_ROW_KIND_DAY) { //--- Apply hover color when this separator row is hovered const bool dayHov = (g_news_hover == NEWS_HOV_ROW_BASE + g_news_visibleRowCount); const color dayBg = dayHov ? g_news_rowHover : g_news_dayHeaderBg; const color dayText = dayHov ? g_news_rowText : g_news_dayHeaderText; g_news_tableTmp.FillRectangle(hL, rT, hR - 1, rB - 1, ColorToARGB(dayBg, 255)); //--- Draw right-pointing chevron when collapsed, down-pointing when expanded const bool collapsed = News_IsDayCollapsed(entry.dateKey); const int chvCx = hL + 14; const int chvCy = rT + NEWS_ROW_H / 2; const int chvDir = collapsed ? NEWS_CHEVRON_RIGHT : NEWS_CHEVRON_DOWN; News_DrawChevron(g_news_tableTmp, chvCx, chvCy, chvDir, ColorToARGB(dayText, 255)); //--- Center bold day label in the separator row const int tH = News_TextHeight("Arial Bold", NEWS_FONT_HEADING); const int tW = News_TextWidth(entry.label, "Arial Bold", NEWS_FONT_HEADING); const int textY = rT + (NEWS_ROW_H - tH) / 2; const int textX = hL + (hW - tW) / 2; News_StampText(g_news_tableTmp, textX, textY, entry.label, "Arial Bold", NEWS_FONT_HEADING, dayText); //--- Mark Remain cache slot inactive for this day row if(g_news_visibleRowCount < NEWS_MAX_VISIBLE_ROWS) { g_news_remainEvTime[g_news_visibleRowCount] = 0; g_news_remainLastStr[g_news_visibleRowCount] = ""; } g_news_visibleRowCount++; continue; } //--- Render event row const int evIdx = entry.eventIdx; if(evIdx < 0 || evIdx >= ArraySize(g_news_displayableEvents)) { //--- Mark bad event slot inactive for the fast path if(g_news_visibleRowCount < NEWS_MAX_VISIBLE_ROWS) { g_news_remainEvTime[g_news_visibleRowCount] = 0; g_news_remainLastStr[g_news_visibleRowCount] = ""; } g_news_visibleRowCount++; continue; } const NewsEvent ev = g_news_displayableEvents[evIdx]; //--- Choose row background color based on hover and alternating pattern const bool hov = (g_news_hover == NEWS_HOV_ROW_BASE + g_news_visibleRowCount); color bg = hov ? g_news_rowHover : ((planIdx % 2 == 0) ? g_news_rowAlt : g_news_panelAlt); g_news_tableTmp.FillRectangle(hL, rT, hR - 1, rB - 1, ColorToARGB(bg, 255)); //--- Determine actual-value directional color based on forecast comparison color actualColor = g_news_rowText; if(ev.hasActual && ev.hasForecast) { if(ev.actual > ev.forecast) actualColor = g_news_actualUp; else if(ev.actual < ev.forecast) actualColor = g_news_actualDown; } //--- Build actual value string: blank for future unreleased, dash for past unreleased const bool isFuture = (ev.eventDateTime > TimeCurrent()); string actualStr; if(ev.hasActual) actualStr = News_FormatValue(true, ev.actual, ev.unit, ev.multiplier, ev.digits); else if(isFuture) actualStr = ""; else actualStr = "-"; //--- Use revised previous value when available, otherwise use original previous const bool prevHas = ev.hasRevised || ev.hasPrevious; const double prevToShow = ev.hasRevised ? ev.revisedPrevious : ev.previous; //--- Populate all nine column value strings string vals[9]; vals[0] = ev.eventDate; vals[1] = ev.eventTime; vals[2] = ev.currency; vals[3] = ""; vals[4] = ev.event; vals[5] = actualStr; vals[6] = News_FormatValue(ev.hasForecast, ev.forecast, ev.unit, ev.multiplier, ev.digits); vals[7] = News_FormatValue(prevHas, prevToShow, ev.unit, ev.multiplier, ev.digits); vals[8] = News_FormatRemain(ev.eventDateTime, TimeCurrent()); //--- Render each column cell int x = hL + 6; const int tH = News_TextHeight("Arial", NEWS_FONT_BODY); const int textY = rT + (NEWS_ROW_H - tH) / 2; for(int c = 0; c < NEWS_COL_COUNT; c++) { const int colW = g_news_colW[c]; if(c == 3) { //--- Draw impact dot as a filled anti-aliased circle const color impClr = News_GetImpactColor(ev.importance); const int dotR = 5; const int dotX = x + colW / 2; const int dotY = rT + NEWS_ROW_H / 2; News_DrawFilledCircle(g_news_tableTmp, dotX, dotY, dotR, ColorToARGB(impClr, 255)); } else { //--- Pick per-column text color color cellColor = g_news_rowText; if(c == 5) cellColor = actualColor; else if(c == 8) { //--- Remain column: subText for past, red for imminent, accent for future const datetime now = TimeCurrent(); if(ev.eventDateTime < now) cellColor = g_news_subText; else if(News_RemainIsImminent(ev.eventDateTime, now)) cellColor = g_news_remainSoon; else cellColor = g_news_accent; } //--- Truncate cell text, reserving extra space on Previous column when revised marker is present string txt = News_FitTextToWidth(vals[c], "Arial", NEWS_FONT_BODY, colW - (c == 7 && ev.hasRevised ? 16 : 4)); News_StampText(g_news_tableTmp, x + 2, textY, txt, "Arial", NEWS_FONT_BODY, cellColor); //--- Cache Remain cell geometry for the timer-tick fast path if(c == 8 && g_news_visibleRowCount < NEWS_MAX_VISIBLE_ROWS) { g_news_remainCellL[g_news_visibleRowCount] = x; g_news_remainCellT[g_news_visibleRowCount] = rT; g_news_remainCellW[g_news_visibleRowCount] = colW; g_news_remainCellH[g_news_visibleRowCount] = NEWS_ROW_H; g_news_remainCellBg[g_news_visibleRowCount] = bg; g_news_remainLastStr[g_news_visibleRowCount] = vals[c]; g_news_remainEvTime[g_news_visibleRowCount] = ev.eventDateTime; } //--- Draw revised-value gold triangle on Previous column when source revised it if(c == 7 && ev.hasRevised) { const int tW = News_TextWidth(txt, "Arial", NEWS_FONT_BODY); const int triCy = rT + NEWS_ROW_H / 2; const int triCx = x + 2 + tW + 7; News_DrawTriangle(g_news_tableTmp, triCx, triCy, NEWS_CHEVRON_LEFT, ColorToARGB(g_news_revisedMark, 255)); //--- Record triangle center for revised-value tooltip hit-test if(g_news_visibleRowCount < NEWS_MAX_VISIBLE_ROWS) { g_news_revTriCx[g_news_visibleRowCount] = triCx; g_news_revTriCy[g_news_visibleRowCount] = triCy; } } else if(c == 7) { //--- Clear triangle slot for rows with no revised marker if(g_news_visibleRowCount < NEWS_MAX_VISIBLE_ROWS) g_news_revTriCx[g_news_visibleRowCount] = -1; } } x += colW; } g_news_visibleRowCount++; } //--- Copy clipped rows region from scratch canvas back to main canvas g_news_tableTmp.CopyRectToCanvas(g_news_canv, seedL, seedT, seedR, seedB); } //+------------------------------------------------------------------+ //| Render scrollbar thumb for the events table | //+------------------------------------------------------------------+ void News_RenderTableScrollbar() { const int rowsTop = News_TableRowsTop(); const int rowsBot = News_TableRowsBottom(); //--- Skip rendering when no scrollbar is needed if(!News_ScrollVisible(g_news_tableScroll)) return; //--- Position the 4px-wide thumb track at the right edge of the table const int laneR = NEWS_DASHBOARD_W - NEWS_SIDE_PAD - 4; const int laneL = laneR - 4; g_news_tableScroll.trackL = laneL; g_news_tableScroll.trackR = laneR; g_news_tableScroll.trackT = rowsTop; g_news_tableScroll.trackB = rowsBot; News_ScrollDraw(g_news_canv, g_news_tableScroll); } //+------------------------------------------------------------------+ //| Render countdown and trade status banner at dashboard bottom | //+------------------------------------------------------------------+ void News_RenderCountdown() { //--- Determine banner text, background, and foreground based on trade mode and state string bannerText = ""; bool active = false; color bg = g_news_panelAlt; color fg = g_news_subText; //--- Show disabled or paused message when auto-trading is off if(tradeMode == NEWS_NO_TRADE || tradeMode == NEWS_PAUSE_TRADING) { bannerText = (tradeMode == NEWS_PAUSE_TRADING) ? "Auto-trading paused" : "Auto-trading disabled"; active = true; } else { string nextName = News_NextNewsName(); //--- Show post-release countdown when a trade was just executed if(g_news_tradeExecuted && g_news_tradedNewsTime > 0) { const int elapsed = (int)(TimeCurrent() - g_news_tradedNewsTime); if(elapsed < 0) { //--- Show active trading banner before news time bannerText = "Trading: " + nextName; bg = g_news_countdownBg; fg = g_news_countdownText; active = true; } else if(elapsed < 15) { //--- Show reset countdown banner for 15 seconds after news bannerText = "News released - resetting in " + IntegerToString(15 - elapsed) + "s"; bg = g_news_countdownReleaseBg; fg = g_news_countdownText; active = true; } } else if(StringLen(nextName) > 0) { //--- Show next eligible event name in the banner bannerText = "Next eligible: " + nextName; bg = g_news_countdownBg; fg = g_news_countdownText; active = true; } else { //--- No eligible event found; show waiting message bannerText = "Awaiting next eligible event"; active = true; } } if(!active) return; //--- Compute footer banner bounds at the bottom of the dashboard const int bH = 26; const int bL = NEWS_SIDE_PAD; const int bR = NEWS_DASHBOARD_W - NEWS_SIDE_PAD; const int bW = bR - bL; const int bY = NEWS_DASHBOARD_H - bH - 8; //--- Draw banner background and center the text label News_FillRoundRect(g_news_canv, bL, bY, bW, bH, 6, ColorToARGB(bg, 230)); const int tW = News_TextWidth(bannerText, "Arial Bold", NEWS_FONT_LABEL); const int tH = News_TextHeight("Arial Bold", NEWS_FONT_LABEL); News_StampTextAA_Wrapper(bannerText, bL + (bW - tW) / 2, bY + (bH - tH) / 2, "Arial Bold", NEWS_FONT_LABEL, fg); } //+------------------------------------------------------------------+ //| Render toast notification with shrinking progress bar | //+------------------------------------------------------------------+ void News_RenderToast() { //--- Skip when no toast is active or it has expired if(StringLen(g_news_toastText) == 0) return; const ulong now = GetTickCount64(); if(now > g_news_toastExpiryMs) return; //--- Compute toast box dimensions based on text size const int padX = 16; const int padY = 8; const string font = "Arial Bold"; const int fsz = 10; const int textW = News_TextWidth(g_news_toastText, font, fsz); const int textH = News_TextHeight(font, fsz); const int barH = 2; const int barGap = 6; const int boxW = textW + 2 * padX; const int boxH = textH + barGap + barH + 2 * padY; const int boxL = (NEWS_DASHBOARD_W - boxW) / 2; const int boxT = NEWS_HEADER_H + 6; //--- Draw toast box with rounded background and border News_FillRoundRect(g_news_canv, boxL, boxT, boxW, boxH, 8, ColorToARGB(g_news_toastBg, 240)); News_DrawRoundRectBorder(g_news_canv, boxL, boxT, boxW, boxH, 8, 1, ColorToARGB(g_news_toastBorder, 255)); //--- Stamp toast message text in success or error color const color textCol = g_news_toastIsError ? g_news_toastError : g_news_toastSuccess; News_StampTextAA_Wrapper(g_news_toastText, boxL + padX, boxT + padY, font, fsz, textCol); //--- Compute progress bar fill ratio from remaining lifetime const int trackL = boxL + padX; const int trackR = boxL + boxW - padX; const int trackW = trackR - trackL; const int trackY = boxT + padY + textH + barGap; const long remaining = (long)g_news_toastExpiryMs - (long)now; double ratio = (double)remaining / 5000.0; if(ratio < 0.0) ratio = 0.0; if(ratio > 1.0) ratio = 1.0; const int fillW = (int)(trackW * ratio); const int fillL = trackL + (trackW - fillW) / 2; //--- Draw full track bar then overlay the shrinking fill from center g_news_canv.FillRectangle(trackL, trackY, trackR - 1, trackY + barH - 1, ColorToARGB(g_news_toastBorder, 255)); if(fillW > 0) g_news_canv.FillRectangle(fillL, trackY, fillL + fillW - 1, trackY + barH - 1, ColorToARGB(textCol, 255)); } //+------------------------------------------------------------------+ //| Fast-path timer tick: refresh only changed Remain cells | //+------------------------------------------------------------------+ bool News_TickRemainCells() { //--- Skip if canvas does not exist or no rows are visible if(!g_news_canvasExists) return false; if(g_news_visibleRowCount <= 0) return false; //--- Establish viewport clip bounds to prevent painting outside the table area const int vpTop = News_TableRowsTop(); const int vpBottom = News_TableRowsBottom(); const datetime now = TimeCurrent(); bool any = false; //--- Process each visible row's Remain cell for(int r = 0; r < g_news_visibleRowCount && r < NEWS_MAX_VISIBLE_ROWS; r++) { //--- Skip day separator slots and invalid event slots if(g_news_remainEvTime[r] == 0) continue; const string newStr = News_FormatRemain(g_news_remainEvTime[r], now); if(newStr == g_news_remainLastStr[r]) continue; //--- Clip cell rect vertically to the table viewport const int cL = g_news_remainCellL[r]; int cT = g_news_remainCellT[r]; const int cW = g_news_remainCellW[r]; int cH = g_news_remainCellH[r]; int cB = cT + cH; if(cT < vpTop) cT = vpTop; if(cB > vpBottom) cB = vpBottom; if(cB - cT <= 0) { //--- Cell is fully outside viewport; update string to suppress retry g_news_remainLastStr[r] = newStr; continue; } cH = cB - cT; //--- Repaint cell background and stamp the updated remain string g_news_canv.FillRectangle(cL, cT, cL + cW - 1, cB - 1, ColorToARGB(g_news_remainCellBg[r], 255)); //--- Select same color logic as the full render color cellColor; if(g_news_remainEvTime[r] < now) cellColor = g_news_subText; else if(News_RemainIsImminent(g_news_remainEvTime[r], now)) cellColor = g_news_remainSoon; else cellColor = g_news_accent; const string txt = News_FitTextToWidth(newStr, "Arial", NEWS_FONT_BODY, cW - 4); const int tH = News_TextHeight("Arial", NEWS_FONT_BODY); //--- Center text on the full row height (partial rows render the same partial text as full render) const int textY = g_news_remainCellT[r] + (g_news_remainCellH[r] - tH) / 2; //--- Only stamp when text baseline falls within the clipped viewport band if(textY >= vpTop && textY + tH <= vpBottom) News_StampTextAA_Wrapper(txt, cL + 2, textY, "Arial", NEWS_FONT_BODY, cellColor); g_news_remainLastStr[r] = newStr; any = true; } //--- Push updated canvas to chart only when at least one cell changed if(any) g_news_canv.Update(); return any; } //+------------------------------------------------------------------+ //| Render entire dashboard from scratch | //+------------------------------------------------------------------+ void News_RenderAll() { //--- Skip if main canvas has not been created if(!g_news_canvasExists) return; //--- Recompute column widths from current dashboard width News_ComputeColumnWidths(); const int dW = NEWS_DASHBOARD_W; const int dH = NEWS_DASHBOARD_H; const int crClear = 8; //--- Erase canvas and fill rounded background shape g_news_canv.Erase(0x00000000); g_news_prim.FillRoundRectSharp(g_news_canv, 0, 0, dW, dH, crClear, ColorToARGB(g_news_bg, 255), 4); //--- Render all interior sections in z-order News_RenderHeader(); News_RenderCountdown(); News_RenderFilterToggles(); News_RenderCurrencyChips(); News_RenderImpactChips(); News_RenderTableHeader(); News_RenderEventRows(); News_RenderTableScrollbar(); //--- Render toast overlay on top of all content News_RenderToast(); //--- Draw right-edge resize handle floating at cursor Y position const bool resizeHov = (g_news_hover == NEWS_HOV_RESIZE_R) || g_news_resizing; if(resizeHov) { const int gripperW = 3; const int gripperH = NEWS_RESIZE_HANDLE_H; const int gripperX = dW - 4; const int safeMin = 8 + 4 + gripperH / 2; const int safeMax = dH - 8 - 4 - gripperH / 2; int cy = g_news_cursorY; if(cy < safeMin) cy = safeMin; if(cy > safeMax) cy = safeMax; const int gripperT = cy - gripperH / 2; const color gripCol = g_news_resizing ? g_news_accent : g_news_borderAccent; News_FillRoundRect(g_news_canv, gripperX, gripperT, gripperW, gripperH, gripperW / 2, ColorToARGB(gripCol, 220)); } //--- Draw bottom-edge resize handle floating at cursor X position const bool resizeHovV = (g_news_hover == NEWS_HOV_RESIZE_B) || g_news_resizingV; if(resizeHovV) { const int gripperH = 3; const int gripperW = NEWS_RESIZE_HANDLE_W; const int gripperY = dH - 4; const int safeMin = 8 + 4 + gripperW / 2; const int safeMax = dW - 8 - 4 - gripperW / 2; int cx = g_news_cursorX; if(cx < safeMin) cx = safeMin; if(cx > safeMax) cx = safeMax; const int gripperL = cx - gripperW / 2; const color gripColV = g_news_resizingV ? g_news_accent : g_news_borderAccent; News_FillRoundRect(g_news_canv, gripperL, gripperY, gripperW, gripperH, gripperH / 2, ColorToARGB(gripColV, 220)); } //--- Build or reuse the cached corner-clear pixel mask static int s_cornerClearX[]; static int s_cornerClearY[]; static int s_cornerClearN = 0; static int s_cornerClearW = -1; static int s_cornerClearH = -1; if(s_cornerClearW != dW || s_cornerClearH != dH) { //--- Pre-size at worst-case capacity to avoid per-pixel ArrayResize const double rdC = (double)crClear; const int cap = 4 * (crClear + 1) * (crClear + 1); ArrayResize(s_cornerClearX, cap); ArrayResize(s_cornerClearY, cap); int n = 0; //--- Walk each of the four corners and collect pixels outside the arc for(int corner = 0; corner < 4; corner++) { int cxC = (corner == 0 || corner == 2) ? crClear : (dW - 1 - crClear); int cyC = (corner == 0 || corner == 1) ? crClear : (dH - 1 - crClear); int xMin = (corner == 0 || corner == 2) ? 0 : (dW - crClear - 1); int xMax = (corner == 0 || corner == 2) ? crClear : (dW - 1); int yMin = (corner == 0 || corner == 1) ? 0 : (dH - crClear - 1); int yMax = (corner == 0 || corner == 1) ? crClear : (dH - 1); for(int yy = yMin; yy <= yMax; yy++) { for(int xx = xMin; xx <= xMax; xx++) { if(xx < 0 || xx >= dW || yy < 0 || yy >= dH) continue; const double ddx = (double)(xx - cxC); const double ddy = (double)(yy - cyC); const double dist = MathSqrt(ddx * ddx + ddy * ddy); if(dist > rdC + 0.5) { s_cornerClearX[n] = xx; s_cornerClearY[n] = yy; n++; } } } } s_cornerClearN = n; s_cornerClearW = dW; s_cornerClearH = dH; } //--- Zero out all corner pixels outside the rounded radius for(int cci = 0; cci < s_cornerClearN; cci++) g_news_canv.PixelSet(s_cornerClearX[cci], s_cornerClearY[cci], 0x00000000); //--- Draw outer rounded border after corner clear so arcs align precisely const uint borderArgb = ColorToARGB(g_news_borderAccent, 255); g_news_prim.DrawRoundRectBorderObStyle(g_news_canv, 0, 0, dW, dH, crClear, borderArgb); //--- Push main canvas to chart g_news_canv.Update(); //--- Render column separators on the overlay canvas News_RenderSeparatorsOverlay(); } #endif // NEWS_RENDER_MQH