chromium/chrome/updater/win/ui/owner_draw_controls.cc

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/updater/win/ui/owner_draw_controls.h"

#include <algorithm>
#include <cstdint>
#include <vector>

#include "base/check.h"
#include "base/check_op.h"
#include "chrome/updater/win/ui/l10n_util.h"
#include "chrome/updater/win/ui/resources/updater_installer_strings.h"
#include "chrome/updater/win/ui/ui_util.h"

namespace updater::ui {

// Returns the system color corresponding to `high_contrast_color_index` if the
// system is in high contrast mode. Otherwise, it returns `normal_color`.
COLORREF GetColor(COLORREF normal_color, int high_contrast_color_index) {
  return IsHighContrastOn() ? ::GetSysColor(high_contrast_color_index)
                            : normal_color;
}

// Returns the system color brush corresponding to `high_contrast_color_index`
// if the system is in high contrast mode. Otherwise, it returns `normal_brush`.
HBRUSH GetColorBrush(const WTL::CBrush& normal_brush,
                     int high_contrast_color_index) {
  return IsHighContrastOn() ? ::GetSysColorBrush(high_contrast_color_index)
                            : HBRUSH{normal_brush};
}

CaptionButton::CaptionButton() = default;
CaptionButton::~CaptionButton() = default;

LRESULT CaptionButton::OnCreate(UINT, WPARAM, LPARAM, BOOL& handled) {
  handled = false;

  tool_tip_window_.Create(m_hWnd);
  CHECK(tool_tip_window_.IsWindow());
  CHECK(!tool_tip_text_.IsEmpty());

  tool_tip_window_.SetDelayTime(TTDT_AUTOMATIC, 2000);
  tool_tip_window_.Activate(TRUE);
  tool_tip_window_.AddTool(m_hWnd, tool_tip_text_.GetString());

  return 0;
}

LRESULT CaptionButton::OnMouseMessage(UINT msg,
                                      WPARAM wparam,
                                      LPARAM lparam,
                                      BOOL& handled) {
  handled = false;

  if (tool_tip_window_.IsWindow()) {
    MSG relay_msg = {m_hWnd, msg, wparam, lparam};
    tool_tip_window_.RelayEvent(&relay_msg);
  }

  return 1;
}

LRESULT CaptionButton::OnEraseBkgnd(UINT, WPARAM, LPARAM, BOOL& handled) {
  // The background and foreground are both rendered in DrawItem().
  handled = true;
  return 1;
}

LRESULT CaptionButton::OnMouseMove(UINT, WPARAM, LPARAM, BOOL& handled) {
  if (!is_tracking_mouse_events_) {
    TRACKMOUSEEVENT tme = {};
    tme.cbSize = sizeof(TRACKMOUSEEVENT);
    tme.dwFlags = TME_HOVER | TME_LEAVE;
    tme.hwndTrack = m_hWnd;
    tme.dwHoverTime = 1;
    is_tracking_mouse_events_ = _TrackMouseEvent(&tme);
  }

  return 0;
}

LRESULT CaptionButton::OnMouseHover(UINT, WPARAM, LPARAM, BOOL& handled) {
  handled = false;

  if (!is_mouse_hovering_) {
    is_mouse_hovering_ = true;
    Invalidate(false);
    UpdateWindow();
  }

  return 0;
}

LRESULT CaptionButton::OnMouseLeave(UINT, WPARAM, LPARAM, BOOL& handled) {
  handled = false;

  TRACKMOUSEEVENT tme = {};
  tme.cbSize = sizeof(TRACKMOUSEEVENT);
  tme.dwFlags = TME_CANCEL | TME_HOVER | TME_LEAVE;
  tme.hwndTrack = m_hWnd;

  _TrackMouseEvent(&tme);

  is_tracking_mouse_events_ = false;
  is_mouse_hovering_ = false;

  Invalidate(false);
  UpdateWindow();

  return 0;
}

void CaptionButton::DrawItem(LPDRAWITEMSTRUCT draw_item_struct) {
  WTL::CDCHandle dc(draw_item_struct->hDC);

  CRect button_rect;
  GetClientRect(&button_rect);

  COLORREF bk_color(is_mouse_hovering_
                        ? GetColor(kCaptionBkHover, COLOR_HIGHLIGHT)
                        : GetColor(bk_color_, COLOR_WINDOW));
  dc.FillSolidRect(&button_rect, bk_color);

  int rgn_width = button_rect.Width() * 12 / 31;
  int rgn_height = button_rect.Height() * 12 / 31;
  WTL::CRgn rgn(GetButtonRgn(rgn_width, rgn_height));

  // Center the button in the outer button rect.
  rgn.OffsetRgn((button_rect.Width() - rgn_width) / 2,
                (button_rect.Height() - rgn_height) / 2);

  dc.FillRgn(rgn, GetColorBrush(foreground_brush_, is_mouse_hovering_
                                                       ? COLOR_HIGHLIGHTTEXT
                                                       : COLOR_BTNTEXT));

  const UINT button_state = draw_item_struct->itemState;
  if (button_state & ODS_FOCUS && button_state & ODS_SELECTED) {
    dc.FrameRect(&button_rect, frame_brush_);
  }
}

COLORREF CaptionButton::bk_color() const {
  return bk_color_;
}

void CaptionButton::set_bk_color(COLORREF bk_color) {
  bk_color_ = bk_color;
}

CString CaptionButton::tool_tip_text() const {
  return tool_tip_text_;
}

void CaptionButton::set_tool_tip_text(const CString& tool_tip_text) {
  tool_tip_text_ = tool_tip_text;
}

CloseButton::CloseButton() {
  set_tool_tip_text(GetLocalizedString(IDS_CLOSE_BUTTON_BASE).c_str());
}

HRGN CloseButton::GetButtonRgn(int rgn_width, int rgn_height) {
  // A single 4x4 rectangular center and criss-crossing 2x2 overlapping
  // rectangles form the close button.
  int square_side = std::min(rgn_width, rgn_height) / 2 * 2;
  int center_point = square_side / 2;

  CRect center_rect(0, 0, 4, 4);
  center_rect.OffsetRect(center_point - 2, center_point - 2);
  WTL::CRgnHandle rgn(::CreateRectRgnIndirect(&center_rect));

  for (int i = 0; i <= square_side - 2; i++) {
    WTL::CRgn rgn_nw_to_se(::CreateRectRgn(i, i, i + 2, i + 2));
    rgn.CombineRgn(rgn_nw_to_se, RGN_OR);

    WTL::CRgn rgn_sw_to_ne(
        ::CreateRectRgn(i, square_side - i - 2, i + 2, square_side - i));
    rgn.CombineRgn(rgn_sw_to_ne, RGN_OR);
  }

  rgn.OffsetRgn((rgn_width - square_side) / 2, (rgn_height - square_side) / 2);
  return rgn;
}

MinimizeButton::MinimizeButton() {
  set_tool_tip_text(GetLocalizedString(IDS_MINIMIZE_BUTTON_BASE).c_str());
}

HRGN MinimizeButton::GetButtonRgn(int rgn_width, int rgn_height) {
  // The Minimize button is a single rectangle.
  CRect minimize_button_rect(0, 0, rgn_width, 2);
  int center_point = rgn_height / 2;
  minimize_button_rect.OffsetRect(0, center_point - 1);
  return ::CreateRectRgnIndirect(&minimize_button_rect);
}

MaximizeButton::MaximizeButton() {
  // Maximize button is not used.
  set_tool_tip_text(L"");
}

HRGN MaximizeButton::GetButtonRgn(int rgn_width, int rgn_height) {
  // Overlapping outer and inner rectangles form the maximize button.
  const RECT maximize_button_rects[] = {{0, 0, rgn_width, rgn_height},
                                        {1, 2, rgn_width - 1, rgn_height - 1}};

  WTL::CRgnHandle rgn(::CreateRectRgnIndirect(&maximize_button_rects[0]));
  WTL::CRgn rgn_temp(::CreateRectRgnIndirect(&maximize_button_rects[1]));
  rgn.CombineRgn(rgn_temp, RGN_DIFF);
  return rgn;
}

OwnerDrawTitleBarWindow::OwnerDrawTitleBarWindow() = default;
OwnerDrawTitleBarWindow::~OwnerDrawTitleBarWindow() = default;

LRESULT OwnerDrawTitleBarWindow::OnCreate(UINT, WPARAM, LPARAM, BOOL& handled) {
  handled = false;

  CreateCaptionButtons();
  return 0;
}

LRESULT OwnerDrawTitleBarWindow::OnDestroy(UINT,
                                           WPARAM,
                                           LPARAM,
                                           BOOL& handled) {
  handled = false;

  if (close_button_.IsWindow()) {
    close_button_.DestroyWindow();
  }

  if (minimize_button_.IsWindow()) {
    minimize_button_.DestroyWindow();
  }

  return 0;
}

LRESULT OwnerDrawTitleBarWindow::OnMouseMove(UINT,
                                             WPARAM wparam,
                                             LPARAM,
                                             BOOL& handled) {
  handled = false;
  if (current_drag_position_.x == -1 || wparam != MK_LBUTTON) {
    return 0;
  }

  CPoint pt;
  ::GetCursorPos(&pt);
  int dx = pt.x - current_drag_position_.x;
  int dy = pt.y - current_drag_position_.y;
  current_drag_position_ = pt;

  MoveWindowToDragPosition(GetParent(), dx, dy);

  return 0;
}

LRESULT OwnerDrawTitleBarWindow::OnLButtonDown(UINT,
                                               WPARAM,
                                               LPARAM,
                                               BOOL& handled) {
  handled = false;
  ::GetCursorPos(&current_drag_position_);
  SetCapture();
  return 0;
}

LRESULT OwnerDrawTitleBarWindow::OnLButtonUp(UINT,
                                             WPARAM,
                                             LPARAM,
                                             BOOL& handled) {
  handled = false;

  current_drag_position_.x = -1;
  current_drag_position_.y = -1;
  ReleaseCapture();

  // Reset the parent to be the active window.
  ::SetActiveWindow(GetParent());
  return 0;
}

LRESULT OwnerDrawTitleBarWindow::OnEraseBkgnd(UINT,
                                              WPARAM wparam,
                                              LPARAM,
                                              BOOL& handled) {
  handled = true;

  WTL::CDC dc(reinterpret_cast<HDC>(wparam));
  CRect rect;
  GetClientRect(&rect);

  dc.FillSolidRect(&rect, GetColor(bk_color_, COLOR_WINDOW));
  return 1;
}

LRESULT OwnerDrawTitleBarWindow::OnClose(WORD, WORD, HWND, BOOL& handled) {
  handled = false;

  ::PostMessage(GetParent(), WM_SYSCOMMAND, MAKEWPARAM(SC_CLOSE, 0), 0);
  return 0;
}

LRESULT OwnerDrawTitleBarWindow::OnMaximize(WORD, WORD, HWND, BOOL& handled) {
  handled = false;

  ::PostMessage(GetParent(), WM_SYSCOMMAND, MAKEWPARAM(SC_CLOSE, 0), 0);
  return 0;
}

LRESULT OwnerDrawTitleBarWindow::OnMinimize(WORD, WORD, HWND, BOOL& handled) {
  handled = false;

  ::PostMessage(GetParent(), WM_SYSCOMMAND, MAKEWPARAM(SC_MINIMIZE, 0), 0);
  return 0;
}

void OwnerDrawTitleBarWindow::CreateCaptionButtons() {
  close_button_.set_bk_color(bk_color_);
  minimize_button_.set_bk_color(bk_color_);

  CRect button_rect(0, 0, ::GetSystemMetrics(SM_CXSIZE),
                    ::GetSystemMetrics(SM_CYSIZE));

  minimize_button_.Create(m_hWnd, button_rect, nullptr,
                          WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, 0,
                          kButtonMinimize);

  close_button_.Create(m_hWnd, button_rect, nullptr,
                       WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, 0, kButtonClose);
  RecalcLayout();
}

// This function handles four button states:
// - Button window does not exist. Nothing to do in this case.
// - Corresponding menu item)does not exist. Hide button.
// - Corresponding menu item disabled. Disable and Show button.
// - Corresponding menu item enabled. Enable and Show button.
void OwnerDrawTitleBarWindow::UpdateButtonState(const WTL::CMenuHandle& menu,
                                                UINT button_sc_id,
                                                const int button_margin,
                                                CaptionButton* button,
                                                CRect* button_rect) {
  CHECK(button);
  CHECK(button_rect);

  if (!button->IsWindow()) {
    return;
  }

  int state = -1;
  if (!menu.IsNull() && menu.IsMenu()) {
    state = menu.GetMenuState(button_sc_id, MF_BYCOMMAND);
  }

  if (state == -1) {
    button->ShowWindow(SW_HIDE);
    return;
  }

  button->EnableWindow(!(state & (MF_GRAYED | MF_DISABLED)));
  button->SetWindowPos(nullptr, button_rect, SWP_NOZORDER | SWP_SHOWWINDOW);
  button_rect->OffsetRect(-button_rect->Width() - button_margin, 0);
}

void OwnerDrawTitleBarWindow::RecalcLayout() {
  CRect title_bar_rect;
  GetClientRect(&title_bar_rect);

  const int button_margin = title_bar_rect.Height() / 5;
  title_bar_rect.DeflateRect(button_margin, button_margin);

  const int button_height = title_bar_rect.Height();
  const int button_width = button_height;

  // Position controls from the Close button to the Minimize button.
  CRect button_rect(title_bar_rect.right - button_width, title_bar_rect.top,
                    title_bar_rect.right, title_bar_rect.bottom);

  WTL::CMenuHandle menu(::GetSystemMenu(GetParent(), false));
  UpdateButtonState(menu, SC_CLOSE, button_margin, &close_button_,
                    &button_rect);
  UpdateButtonState(menu, SC_MINIMIZE, button_margin, &minimize_button_,
                    &button_rect);
}

void OwnerDrawTitleBarWindow::MoveWindowToDragPosition(HWND hwnd,
                                                       int dx,
                                                       int dy) {
  CRect rect;
  ::GetWindowRect(hwnd, &rect);

  rect.OffsetRect(dx, dy);
  ::SetWindowPos(hwnd, nullptr, rect.left, rect.top, 0, 0,
                 SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);
}

COLORREF OwnerDrawTitleBarWindow::bk_color() const {
  return bk_color_;
}

void OwnerDrawTitleBarWindow::set_bk_color(COLORREF bk_color) {
  bk_color_ = bk_color;
}

OwnerDrawTitleBar::OwnerDrawTitleBar() = default;

OwnerDrawTitleBar::~OwnerDrawTitleBar() = default;

void OwnerDrawTitleBar::CreateOwnerDrawTitleBar(HWND parent_hwnd,
                                                HWND title_bar_spacer_hwnd,
                                                COLORREF bk_color) {
  CHECK(parent_hwnd);

  CRect title_bar_client_rect =
      ComputeTitleBarClientRect(parent_hwnd, title_bar_spacer_hwnd);

  // This title bar is a child window and occupies the top portion of the parent
  // dialog box window. DS_MODALFRAME and WS_BORDER are incompatible with this
  // title bar. WS_DLGFRAME is recommended as well.
  const LONG parent_style = ::GetWindowLong(parent_hwnd, GWL_STYLE);
  CHECK(!(parent_style & DS_MODALFRAME));
  CHECK(!(parent_style & WS_BORDER));
  CHECK(parent_style & WS_DLGFRAME);

  title_bar_window_.set_bk_color(bk_color);
  title_bar_window_.Create(
      parent_hwnd, title_bar_client_rect, nullptr,
      WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);
}

void OwnerDrawTitleBar::RecalcLayout() {
  CHECK(title_bar_window_.IsWindow());
  title_bar_window_.RecalcLayout();
}

CRect OwnerDrawTitleBar::ComputeTitleBarClientRect(HWND parent_hwnd,
                                                   HWND title_bar_spacer_hwnd) {
  CHECK(parent_hwnd);

  CRect parent_client_rect;
  ::GetClientRect(parent_hwnd, &parent_client_rect);
  CRect title_bar_client_rect(parent_client_rect);

  CRect title_bar_spacer_client_rect;
  ::GetClientRect(title_bar_spacer_hwnd, &title_bar_spacer_client_rect);
  const int title_bar_height(title_bar_spacer_client_rect.Height());

  title_bar_client_rect.bottom = title_bar_client_rect.top + title_bar_height;

  return title_bar_client_rect;
}

CustomDlgColors::CustomDlgColors() = default;
CustomDlgColors::~CustomDlgColors() = default;

void CustomDlgColors::SetCustomDlgColors(COLORREF text_color,
                                         COLORREF bk_color) {
  text_color_ = text_color;
  bk_color_ = bk_color;

  CHECK(bk_brush_.IsNull());
  bk_brush_.CreateSolidBrush(bk_color_);
}

LRESULT CustomDlgColors::OnCtrlColor(UINT,
                                     WPARAM wparam,
                                     LPARAM,
                                     BOOL& handled) {
  handled = true;

  WTL::CDCHandle dc(reinterpret_cast<HDC>(wparam));
  SetBkColor(dc, GetColor(bk_color_, COLOR_WINDOW));
  SetTextColor(dc, GetColor(text_color_, COLOR_WINDOWTEXT));

  return reinterpret_cast<LRESULT>(GetColorBrush(bk_brush_, COLOR_WINDOW));
}

CustomProgressBarCtrl::CustomProgressBarCtrl()
    : empty_frame_brush_(::CreateSolidBrush(kProgressEmptyFrameColor)) {}

CustomProgressBarCtrl::~CustomProgressBarCtrl() = default;

LRESULT CustomProgressBarCtrl::OnEraseBkgnd(UINT,
                                            WPARAM,
                                            LPARAM,
                                            BOOL& handled) {
  // The background and foreground are both rendered in OnPaint().
  handled = true;
  return 1;
}

void CustomProgressBarCtrl::GradientFill(HDC dc,
                                         const RECT& rect,
                                         COLORREF top_color,
                                         COLORREF bottom_color) {
  TRIVERTEX tri_vertex[] = {
      {rect.left, rect.top, static_cast<COLOR16>(GetRValue(top_color) << 8),
       static_cast<COLOR16>(GetGValue(top_color) << 8),
       static_cast<COLOR16>(GetBValue(top_color) << 8), 0},
      {rect.right, rect.bottom,
       static_cast<COLOR16>(GetRValue(bottom_color) << 8),
       static_cast<COLOR16>(GetGValue(bottom_color) << 8),
       static_cast<COLOR16>(GetBValue(bottom_color) << 8), 0},
  };

  GRADIENT_RECT gradient_rect = {0, 1};

  ::GradientFill(dc, tri_vertex, 2, &gradient_rect, 1, GRADIENT_FILL_RECT_V);
}

LRESULT CustomProgressBarCtrl::OnPaint(UINT, WPARAM, LPARAM, BOOL& handled) {
  handled = true;

  CRect client_rect;
  GetClientRect(&client_rect);

  CRect progress_bar_rect = client_rect;

  const int kBarWidth = kMaxPosition - kMinPosition;
  const LONG bar_rect_right =
      client_rect.left +
      client_rect.Width() * (current_position_ - kMinPosition) / kBarWidth;
  progress_bar_rect.right = std::min(bar_rect_right, client_rect.right);

  if (GetStyle() & PBS_MARQUEE) {
    LONG bar_rect_left(bar_rect_right -
                       client_rect.Width() * kMarqueeWidth / kBarWidth);
    progress_bar_rect.left = std::max(bar_rect_left, client_rect.left);
    CHECK_LE(progress_bar_rect.left, progress_bar_rect.right);
  }

  WTL::CRgn rgn = ::CreateRectRgnIndirect(&client_rect);
  WTL::CRgn rgn_temp = ::CreateRectRgnIndirect(&progress_bar_rect);
  rgn.CombineRgn(rgn_temp, RGN_DIFF);

  // Using a memory device context eliminates flicker.
  WTL::CPaintDC dcPaint(m_hWnd);
  WTL::CMemoryDC dc(dcPaint, client_rect);

  // FillRgn appears to have a bug with RTL/mirroring where it does not paint
  // the first pixel of the rightmost rectangle with the 'rgn' created above.
  // Since the region is rectangles, instead of using FillRgn, this code gets
  // all the rectangles in the 'rgn' and fills them by hand.
  const int rgndata_size = rgn.GetRegionData(nullptr, 0);
  CHECK(rgndata_size);
  std::vector<uint8_t> rgndata_buff(rgndata_size);
  RGNDATA& rgndata = *reinterpret_cast<RGNDATA*>(&rgndata_buff[0]);

  if (rgn.GetRegionData(&rgndata, rgndata_size)) {
    for (DWORD count = 0; count < rgndata.rdh.nCount; count++) {
      CRect r = reinterpret_cast<RECT*>(rgndata.Buffer + count * sizeof(RECT));
      CRect bottom_edge_rect = r;

      // Have a 2-pixel bottom edge.
      r.DeflateRect(0, 0, 0, 2);
      bottom_edge_rect.top = r.bottom;

      WTL::CBrushHandle bottom_edge_brush(
          reinterpret_cast<HBRUSH>(GetParent().SendMessage(
              WM_CTLCOLORSTATIC, reinterpret_cast<WPARAM>(dc.m_hDC),
              reinterpret_cast<LPARAM>(m_hWnd))));
      dc.FillRect(&bottom_edge_rect, bottom_edge_brush);

      dc.FrameRect(r, empty_frame_brush_);
      r.DeflateRect(1, 1);
      dc.FillSolidRect(r, GetColor(empty_fill_color_, COLOR_WINDOWTEXT));
    }
  }

  if (progress_bar_rect.IsRectEmpty()) {
    return 0;
  }

  // Have a 2-pixel bottom shadow with a gradient fill.
  CRect shadow_rect = progress_bar_rect;
  shadow_rect.top = shadow_rect.bottom - 2;
  GradientFill(dc, shadow_rect, kProgressShadowDarkColor,
               kProgressShadowLightColor);

  // Have a 1-pixel left highlight.
  CRect left_highlight_rect = progress_bar_rect;
  left_highlight_rect.right = left_highlight_rect.left + 1;
  dc.FillSolidRect(left_highlight_rect, kProgressLeftHighlightColor);

  // Adjust progress bar rectangle to accommodate the highlight and shadow.
  // Then draw the outer and inner frames. Then fill in the bar.
  progress_bar_rect.DeflateRect(1, 0, 0, 2);
  GradientFill(dc, progress_bar_rect, kProgressOuterFrameLight,
               kProgressOuterFrameDark);

  progress_bar_rect.DeflateRect(1, 1);
  GradientFill(dc, progress_bar_rect, kProgressInnerFrameLight,
               kProgressInnerFrameDark);

  progress_bar_rect.DeflateRect(1, 1);
  GradientFill(dc, progress_bar_rect, GetColor(bar_color_light_, COLOR_WINDOW),
               GetColor(bar_color_dark_, COLOR_WINDOW));

  return 0;
}

LRESULT CustomProgressBarCtrl::OnTimer(UINT,
                                       WPARAM event_id,
                                       LPARAM,
                                       BOOL& handled) {
  handled = true;

  if (event_id != kMarqueeTimerId) {
    handled = false;
    return 0;
  }

  ::SendMessage(m_hWnd, PBM_SETPOS, 0, 0L);
  return 0;
}

LRESULT CustomProgressBarCtrl::OnSetPos(UINT,
                                        WPARAM new_position,
                                        LPARAM,
                                        BOOL& handled) {
  // To allow accessibility to show the correct progress values, we pass
  // PBM_SETPOS to the underlying Win32 control.
  handled = false;

  int old_position = current_position_;

  if (GetStyle() & PBS_MARQUEE) {
    current_position_++;
    if (current_position_ >= (kMaxPosition + kMarqueeWidth)) {
      current_position_ = kMinPosition;
    }
  } else {
    current_position_ = std::min(static_cast<int>(new_position), kMaxPosition);
  }

  if (current_position_ < kMinPosition) {
    current_position_ = kMinPosition;
  }

  RedrawWindow();

  return old_position;
}

// Calling WM_SETBARCOLOR will convert the progress bar into a solid colored
// bar. i.e., no gradient.
LRESULT CustomProgressBarCtrl::OnSetBarColor(UINT,
                                             WPARAM,
                                             LPARAM bar_color,
                                             BOOL& handled) {
  handled = true;

  COLORREF old_bar_color = bar_color_light_;
  bar_color_light_ = bar_color_dark_ = static_cast<COLORREF>(bar_color);

  RedrawWindow();

  return old_bar_color;
}

LRESULT CustomProgressBarCtrl::OnSetBkColor(UINT,
                                            WPARAM,
                                            LPARAM empty_fill_color,
                                            BOOL& handled) {
  handled = true;

  COLORREF old_empty_fill_color = kProgressEmptyFillColor;
  empty_fill_color_ = static_cast<COLORREF>(empty_fill_color);

  RedrawWindow();

  return old_empty_fill_color;
}

LRESULT CustomProgressBarCtrl::OnSetMarquee(UINT,
                                            WPARAM is_set_marquee,
                                            LPARAM update_msec,
                                            BOOL& handled) {
  // To allow accessibility to show the correct progress values, we pass
  // PBM_SETMARQUEE to the underlying Win32 control.
  handled = false;

  if (is_set_marquee && !is_marquee_mode_) {
    current_position_ = kMinPosition;
    SetTimer(kMarqueeTimerId, static_cast<UINT>(update_msec));
    is_marquee_mode_ = true;
  } else if (!is_set_marquee && is_marquee_mode_) {
    KillTimer(kMarqueeTimerId);
    is_marquee_mode_ = false;
  }

  RedrawWindow();

  return is_set_marquee;
}

}  // namespace updater::ui