Pārlūkot izejas kodu

Issue 2345 – Dark theme for Login dialog

https://winscp.net/tracker/2345

Comboboxes with csDropDownList style do not reflect the Color, but do with csOwnerDrawFixed.
csOwnerDrawFixed seems to behave the same, except for the height, which needs to be explicitly set slightly higher
Buttons are still white - they'd have to be custom-drawn (using "Windows10 Dark" custom style)?)
SetParentColor is probably obsoleted by 3a887624

Source commit: 851489ffff36845467f664a8a4a08d0556c1d2eb
Martin Prikryl 7 mēneši atpakaļ
vecāks
revīzija
a90827e611

+ 2 - 7
source/forms/CustomScpExplorer.cpp

@@ -10619,13 +10619,8 @@ TDragDropFilesEx * __fastcall TCustomScpExplorerForm::CreateDragDropFilesEx()
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::UpdateDarkMode()
 {
-  if (IsWin10Build(19041))
-  {
-    FImmersiveDarkMode = WinConfiguration->UseDarkTheme();
-    BOOL DarkMode = FImmersiveDarkMode ? TRUE : FALSE;
-    const int DWMWA_USE_IMMERSIVE_DARK_MODE = 20;
-    DwmSetWindowAttribute(Handle, DWMWA_USE_IMMERSIVE_DARK_MODE, &DarkMode, sizeof(DarkMode));
-  }
+  FImmersiveDarkMode = WinConfiguration->UseDarkTheme();
+  UseDarkMode(this);
 }
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::CreateWnd()

+ 14 - 0
source/forms/Login.cpp

@@ -1900,6 +1900,14 @@ void __fastcall TLoginDialog::SessionTreeCustomDrawItem(
   }
 
   Sender->Canvas->Font->Style = Styles;
+
+  DebugAssert(Sender == SessionTree);
+  if (Node->Selected && !SessionTree->Focused() && UseDarkModeForControl(SessionTree))
+  {
+    // Otherwise it is rendered black on black. See also TCustomDriveView.InternalOnDrawItem.
+    SessionTree->Canvas->Font->Color = SessionTree->Font->Color;
+  }
+
   DefaultDraw = true;
 }
 //---------------------------------------------------------------------------
@@ -3397,3 +3405,9 @@ void __fastcall TLoginDialog::SitesIncrementalSearchPanelContextPopup(TObject *
   MenuPopup(Sender, MousePos, Handled);
 }
 //---------------------------------------------------------------------------
+void __fastcall TLoginDialog::CreateWnd()
+{
+  TForm::CreateWnd();
+  ApplyColorMode(this);
+}
+//---------------------------------------------------------------------------

+ 6 - 3
source/forms/Login.dfm

@@ -258,7 +258,8 @@ object LoginDialog: TLoginDialog
           Top = 40
           Width = 164
           Height = 23
-          Style = csDropDownList
+          Style = csOwnerDrawFixed
+          ItemHeight = 17
           TabOrder = 0
           OnChange = TransferProtocolComboChange
           Items.Strings = (
@@ -273,7 +274,8 @@ object LoginDialog: TLoginDialog
           Top = 40
           Width = 204
           Height = 23
-          Style = csDropDownList
+          Style = csOwnerDrawFixed
+          ItemHeight = 17
           TabOrder = 2
           OnChange = EncryptionComboChange
           Items.Strings = (
@@ -286,7 +288,8 @@ object LoginDialog: TLoginDialog
           Top = 40
           Width = 204
           Height = 23
-          Style = csDropDownList
+          Style = csOwnerDrawFixed
+          ItemHeight = 17
           TabOrder = 3
           OnChange = EncryptionComboChange
           Items.Strings = (

+ 1 - 0
source/forms/Login.h

@@ -445,6 +445,7 @@ protected:
   void __fastcall EditSession();
   void __fastcall Login();
   void SetSiteSearch(TIncrementalSearch SiteSearch);
+  virtual void __fastcall CreateWnd();
   __property TSessionData * SelectedSession  = { read=GetSelectedSession };
 
   INTERFACE_HOOK;

+ 159 - 0
source/windows/GUITools.cpp

@@ -2419,6 +2419,43 @@ static int HideAccelFlag(TControl * Control)
   return Result;
 }
 //---------------------------------------------------------------------------
+//---------------------------------------------------------------------------
+class TDarkUxThemeStyle : public TUxThemeStyle
+{
+public:
+  static TDarkUxThemeStyle * Instance();
+
+protected:
+  virtual bool __fastcall DoDrawText(
+    HDC DC, const TThemedElementDetails & Details, const UnicodeString S, TRect & R, TTextFormat Flags,
+    const TStyleTextOptions & Options, int DPI);
+
+private:
+  static std::unique_ptr<TDarkUxThemeStyle> FInstance;
+};
+//---------------------------------------------------------------------------
+std::unique_ptr<TDarkUxThemeStyle> TDarkUxThemeStyle::FInstance;
+//---------------------------------------------------------------------------
+TDarkUxThemeStyle * TDarkUxThemeStyle::Instance()
+{
+  if (FInstance.get() == NULL)
+  {
+    FInstance.reset(new TDarkUxThemeStyle());
+  }
+  return FInstance.get();
+}
+//---------------------------------------------------------------------------
+bool __fastcall TDarkUxThemeStyle::DoDrawText(
+  HDC DC, const TThemedElementDetails & Details, const UnicodeString S, TRect & R, TTextFormat Flags,
+  const TStyleTextOptions & AOptions, int DPI)
+{
+  TStyleTextOptions Options = AOptions;
+  Options.TextColor = GetWindowTextColor(GetBtnFaceColor());
+  Options.Flags << stfTextColor;
+  return TUxThemeStyle::DoDrawText(DC, Details, S, R, Flags, Options, DPI);
+}
+//---------------------------------------------------------------------------
+//---------------------------------------------------------------------------
 __fastcall TUIStateAwareLabel::TUIStateAwareLabel(TComponent * AOwner) :
   TLabel(AOwner)
 {
@@ -2458,6 +2495,120 @@ void __fastcall TUIStateAwareLabel::Dispatch(void * AMessage)
   }
 }
 //---------------------------------------------------------------------------
+//---------------------------------------------------------------------------
+template<typename T>
+bool HandleMessageByDarkStyleHook(TMessage & Msg, TWinControl * Control, std::unique_ptr<T> & StyleHook)
+{
+  bool Result = false;
+  if (Control->HandleAllocated() &&
+      !Control->ComponentState.Contains(csDestroying) &&
+      !Control->ControlState.Contains(csDestroyingHandle) &&
+      !Control->ControlStyle.Contains(csOverrideStylePaint) &&
+      UseDarkModeForControl(Control))
+  {
+    if (StyleHook.get() == NULL)
+    {
+      StyleHook.reset(new T(Control));
+    }
+    Result = StyleHook->HandleMessage(Msg);
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
+//---------------------------------------------------------------------------
+class TDarkGroupBoxStyleHook : public TGroupBoxStyleHook
+{
+public:
+  __fastcall virtual TDarkGroupBoxStyleHook(TWinControl * AControl) :
+    TGroupBoxStyleHook(AControl)
+  {
+  }
+
+protected:
+  virtual TCustomStyleServices * __fastcall StyleServices()
+  {
+    return TDarkUxThemeStyle::Instance();
+  }
+};
+//---------------------------------------------------------------------------
+class TGroupBoxEx : public TGroupBox
+{
+protected:
+  virtual void __fastcall PaintWindow(HDC DC);
+  virtual void __fastcall WndProc(TMessage & Msg);
+private:
+  std::unique_ptr<TDarkGroupBoxStyleHook> FStyleHook;
+};
+//---------------------------------------------------------------------------
+void __fastcall TGroupBoxEx::WndProc(TMessage & Msg)
+{
+  if (!HandleMessageByDarkStyleHook(Msg, this, FStyleHook))
+  {
+    TGroupBox::WndProc(Msg);
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TGroupBoxEx::PaintWindow(HDC DC)
+{
+  if (UseDarkModeForControl(this))
+  {
+    std::unique_ptr<TCanvas> Canvas(new TCanvas());
+    Canvas->Handle = DC;
+    Canvas->Font = Font;
+
+    TRect Rect = ClientRect;
+    Canvas->Brush->Style = bsSolid;
+    Canvas->Brush->Color = GetBtnFaceColor();
+    Canvas->FillRect(Rect);
+  }
+
+  TGroupBox::PaintWindow(DC);
+}
+//---------------------------------------------------------------------------
+//---------------------------------------------------------------------------
+class TDarkCheckBoxStyleHook : public TCheckBoxStyleHook
+{
+public:
+  __fastcall virtual TDarkCheckBoxStyleHook(TWinControl * AControl);
+protected:
+  virtual void __fastcall PaintBackground(TCanvas * Canvas);
+  virtual TCustomStyleServices * __fastcall StyleServices();
+};
+//---------------------------------------------------------------------------
+class TCheckBoxEx : public TCheckBox
+{
+protected:
+  virtual void __fastcall WndProc(TMessage & Msg);
+private:
+  std::unique_ptr<TDarkCheckBoxStyleHook> FStyleHook;
+};
+//---------------------------------------------------------------------------
+__fastcall TDarkCheckBoxStyleHook::TDarkCheckBoxStyleHook(TWinControl * AControl) :
+  TCheckBoxStyleHook(AControl)
+{
+}
+//---------------------------------------------------------------------------
+void __fastcall TDarkCheckBoxStyleHook::PaintBackground(TCanvas * Canvas)
+{
+  Canvas->Brush->Style = bsSolid;
+  Canvas->Brush->Color = GetBtnFaceColor();
+  Canvas->FillRect(Rect(0, 0, Control->Width, Control->Height));
+}
+//---------------------------------------------------------------------------
+TCustomStyleServices * __fastcall TDarkCheckBoxStyleHook::StyleServices()
+{
+  return TDarkUxThemeStyle::Instance();
+}
+//---------------------------------------------------------------------------
+void __fastcall TCheckBoxEx::WndProc(TMessage & Msg)
+{
+  if (!HandleMessageByDarkStyleHook(Msg, this, FStyleHook))
+  {
+    TCheckBox::WndProc(Msg);
+  }
+}
+//---------------------------------------------------------------------------
+//---------------------------------------------------------------------------
 void __fastcall FindComponentClass(
   void *, TReader *, const UnicodeString DebugUsedArg(ClassName), TComponentClass & ComponentClass)
 {
@@ -2469,6 +2620,14 @@ void __fastcall FindComponentClass(
   {
     ComponentClass = __classid(TUIStateAwareComboBox);
   }
+  else if (ComponentClass == __classid(TGroupBox))
+  {
+    ComponentClass = __classid(TGroupBoxEx);
+  }
+  else if (ComponentClass == __classid(TCheckBox))
+  {
+    ComponentClass = __classid(TCheckBoxEx);
+  }
 }
 //---------------------------------------------------------------------------
 bool CanShowTimeEstimate(TDateTime StartTime)

+ 120 - 75
source/windows/VCLCommon.cpp

@@ -32,6 +32,33 @@ const UnicodeString LinkAppLabelMark(TraceInitStr(UnicodeString(L" ") + ContextS
 //---------------------------------------------------------------------------
 #pragma package(smart_init)
 //---------------------------------------------------------------------------
+class TFormCustomizationComponent : public TComponent
+{
+public:
+  __fastcall TFormCustomizationComponent() :
+    TComponent(NULL)
+  {
+    WindowStateBeforeMimimize = wsNormal;
+    DarkMode = false;
+  }
+
+  TWindowState WindowStateBeforeMimimize;
+  bool DarkMode;
+};
+//---------------------------------------------------------------------------
+static TFormCustomizationComponent * GetFormCustomizationComponent(TCustomForm * Form)
+{
+  TFormCustomizationComponent * FormCustomizationComponent =
+    dynamic_cast<TFormCustomizationComponent *>(Form->FindComponent(TFormCustomizationComponent::QualifiedClassName()));
+  if (FormCustomizationComponent == NULL)
+  {
+    FormCustomizationComponent = new TFormCustomizationComponent();
+    FormCustomizationComponent->Name = TFormCustomizationComponent::QualifiedClassName();
+    Form->InsertComponent(FormCustomizationComponent);
+  }
+  return FormCustomizationComponent;
+}
+//---------------------------------------------------------------------------
 void __fastcall FixListColumnWidth(TListView * TListView, int Index)
 {
   if (Index < 0)
@@ -202,44 +229,20 @@ void __fastcall AutoSizeListColumnsWidth(TListView * ListView, int ColumnToShrin
   }
 }
 //---------------------------------------------------------------------------
-static void __fastcall SetParentColor(TControl * Control)
+bool IsWindowColorControl(TControl * Control)
 {
-  TColor Color = clBtnFace;
-  if (UseThemes)
-  {
-    bool OnTabSheet = false;
-    TWinControl * Parent = Control->Parent;
-    while ((Parent != NULL) && !OnTabSheet)
-    {
-      TTabSheet * TabSheet = dynamic_cast<TTabSheet *>(Parent);
-      OnTabSheet = (TabSheet != NULL) && TabSheet->TabVisible;
-      Parent = Parent->Parent;
-    }
-
-    if (OnTabSheet)
-    {
-      HTHEME Theme = OpenThemeData(NULL, L"tab");
-      if (Theme != NULL)
-      {
-        COLORREF RGB;
-        // XP with classic theme: Does not get past OpenThemeData, clBtnFace is exact color
-        // XP with XP theme: not the exact color (probably same as clBtnFace), but close
-        // Vista - ok
-        // 2016 without desktop - ok
-        // 7 with classic and high contrast themes: Do not get past OpenThemeData, clBtnFace is exact color
-        // 7 with 7 and basic themes - ok
-        // 10 with high contrast themes - ok (note the difference to 7 with high contract themes)
-        // 10 - ok
-        if (GetThemeColor(Theme, TABP_AEROWIZARDBODY, TIS_NORMAL, TMT_FILLCOLOR, &RGB) == S_OK)
-        {
-          Color = static_cast<TColor>(RGB);
-        }
-        CloseThemeData(Theme);
-      }
-    }
-  }
-
-  ((TEdit*)Control)->Color = Color;
+  return
+    (dynamic_cast<TCustomEdit *>(Control) != NULL) ||
+    (dynamic_cast<TCustomComboBox *>(Control) != NULL) ||
+    (dynamic_cast<TCustomListView *>(Control) != NULL) ||
+    (dynamic_cast<TCustomTreeView *>(Control) != NULL);
+}
+//---------------------------------------------------------------------------
+bool UseDarkModeForControl(TControl * Control)
+{
+  return
+    WinConfiguration->UseDarkTheme() && // optimization
+    GetFormCustomizationComponent(GetParentForm(Control))->DarkMode;
 }
 //---------------------------------------------------------------------------
 void __fastcall EnableControl(TControl * Control, bool Enable)
@@ -258,24 +261,23 @@ void __fastcall EnableControl(TControl * Control, bool Enable)
     Control->Enabled = Enable;
   }
 
-  if ((dynamic_cast<TCustomEdit *>(Control) != NULL) ||
-      (dynamic_cast<TCustomComboBox *>(Control) != NULL) ||
-      (dynamic_cast<TCustomListView *>(Control) != NULL) ||
-      (dynamic_cast<TCustomTreeView *>(Control) != NULL))
+  if (IsWindowColorControl(Control))
   {
+    bool DarkMode = UseDarkModeForControl(Control);
+    TColor Color;
     if (Enable)
     {
-      ((TEdit*)Control)->Color = clWindow;
+      Color = DarkMode ? GetWindowColor() : clWindow;
     }
     else
     {
-      // This does not work for list view with
-      // LVS_EX_DOUBLEBUFFER (TCustomDirView).
-      // It automatically gets gray background.
-      // Though on Windows 7, the control has to be disabled
-      // only after it is showing already (see TCustomScpExplorerForm::UpdateControls())
-      ((TEdit*)Control)->Color = clBtnFace;
+      // This does not work for list view with LVS_EX_DOUBLEBUFFER (TCustomDirView).
+      // It automatically gets gray background. Though on Windows 7, the control has to be disabled
+      // only after it is showing already (see TCustomScpExplorerForm::UpdateControls()).
+      // But we do not use this code anymore for the main window anyway.
+      Color = DarkMode ? GetBtnFaceColor() : clBtnFace;
     }
+    ((TEdit*)Control)->Color = Color;
   }
 };
 //---------------------------------------------------------------------------
@@ -350,6 +352,7 @@ void __fastcall DoReadOnlyControl(TControl * Control, bool ReadOnly, bool Color)
 {
   if (dynamic_cast<TCustomEdit *>(Control) != NULL)
   {
+    bool DarkMode = UseDarkModeForControl(Control);
     TEdit * Edit = static_cast<TEdit *>(Control);
     Edit->ReadOnly = ReadOnly;
     TMemo * Memo = dynamic_cast<TMemo *>(Control);
@@ -374,7 +377,7 @@ void __fastcall DoReadOnlyControl(TControl * Control, bool ReadOnly, bool Color)
     {
       if (Color)
       {
-        SetParentColor(Control);
+        Edit->Color = DarkMode ? GetBtnFaceColor() : clBtnFace;
       }
       if (Memo != NULL)
       {
@@ -408,7 +411,7 @@ void __fastcall DoReadOnlyControl(TControl * Control, bool ReadOnly, bool Color)
     {
       if (Color)
       {
-        Edit->Color = clWindow;
+        Edit->Color = DarkMode ? GetWindowColor() : clWindow;
       }
       // not supported atm, we need to persist previous value of WantReturns
       DebugAssert(Memo == NULL);
@@ -527,6 +530,7 @@ class TPublicControl : public TControl
 {
 friend void __fastcall RealignControl(TControl * Control);
 friend TCanvas * CreateControlCanvas(TControl * Control);
+friend void ApplyDarkModeOnControl(TControl * Control);
 };
 //---------------------------------------------------------------------
 class TPublicForm : public TForm
@@ -613,31 +617,6 @@ static void __fastcall ChangeControlScale(TControl * Control)
 typedef std::pair<int, int> TRatio;
 typedef std::map<TRatio, TRatio > TRatioMap;
 //---------------------------------------------------------------------------
-class TFormCustomizationComponent : public TComponent
-{
-public:
-  __fastcall TFormCustomizationComponent() :
-    TComponent(NULL)
-  {
-    WindowStateBeforeMimimize = wsNormal;
-  }
-
-  TWindowState WindowStateBeforeMimimize;
-};
-//---------------------------------------------------------------------------
-static TFormCustomizationComponent * GetFormCustomizationComponent(TForm * Form)
-{
-  TFormCustomizationComponent * FormCustomizationComponent =
-    dynamic_cast<TFormCustomizationComponent *>(Form->FindComponent(TFormCustomizationComponent::QualifiedClassName()));
-  if (FormCustomizationComponent == NULL)
-  {
-    FormCustomizationComponent = new TFormCustomizationComponent();
-    FormCustomizationComponent->Name = TFormCustomizationComponent::QualifiedClassName();
-    Form->InsertComponent(FormCustomizationComponent);
-  }
-  return FormCustomizationComponent;
-}
-//---------------------------------------------------------------------------
 static void __fastcall ChangeFormPixelsPerInch(TForm * Form)
 {
 
@@ -1110,6 +1089,72 @@ void __fastcall ResetSystemSettings(TForm * /*Control*/)
   // noop
 }
 //---------------------------------------------------------------------------
+void ApplyDarkModeOnControl(TControl * Control)
+{
+  TPublicControl * PublicControl = static_cast<TPublicControl *>(Control);
+
+  TColor BtnFaceColor = GetBtnFaceColor();
+  if (dynamic_cast<TForm *>(Control) != NULL)
+  {
+    DebugAssert((PublicControl->Color == clBtnFace) || (PublicControl->Color == BtnFaceColor));
+    PublicControl->Color = BtnFaceColor;
+    PublicControl->Font->Color = GetWindowTextColor(PublicControl->Color);
+  }
+
+  if (dynamic_cast<TPanel *>(Control) != NULL)
+  {
+    DebugAssert((PublicControl->Color == clBtnFace) || (PublicControl->Color == BtnFaceColor));
+    DebugAssert(!PublicControl->ParentColor);
+    PublicControl->Color = BtnFaceColor;
+  }
+
+  if (IsWindowColorControl(Control))
+  {
+    TColor WindowColor = GetWindowColor();
+    DebugAssert((PublicControl->Color == clWindow) || (PublicControl->Color == WindowColor));
+    DebugAssert(!PublicControl->ParentColor);
+    PublicControl->Color = WindowColor;
+  }
+
+  TWinControl * WinControl = dynamic_cast<TWinControl *>(Control);
+  if (WinControl != NULL)
+  {
+    if (dynamic_cast<TTreeView *>(WinControl) != NULL)
+    {
+      // for dark scrollbars
+      WinControl->HandleNeeded();
+      AllowDarkModeForWindow(WinControl, true);
+    }
+
+    for (int Index = 0; Index < WinControl->ControlCount; Index++)
+    {
+      ApplyDarkModeOnControl(WinControl->Controls[Index]);
+    }
+  }
+}
+//---------------------------------------------------------------------------
+void UseDarkMode(TForm * Form)
+{
+  if (IsWin10Build(19041))
+  {
+    BOOL DarkMode = WinConfiguration->UseDarkTheme() ? TRUE : FALSE;
+    const int DWMWA_USE_IMMERSIVE_DARK_MODE = 20;
+    DebugAssert(Form->HandleAllocated());
+    DwmSetWindowAttribute(Form->Handle, DWMWA_USE_IMMERSIVE_DARK_MODE, &DarkMode, sizeof(DarkMode));
+  }
+}
+//---------------------------------------------------------------------------
+void ApplyColorMode(TForm * Form)
+{
+  UseDarkMode(Form);
+  if (WinConfiguration->UseDarkTheme())
+  {
+    GetFormCustomizationComponent(Form)->DarkMode = true;
+    ApplyDarkModeOnControl(Form);
+  }
+}
+//---------------------------------------------------------------------------
+//---------------------------------------------------------------------------
 struct TShowAsModalStorage
 {
   bool TriggerModalFinished;

+ 3 - 0
source/windows/VCLCommon.h

@@ -13,6 +13,7 @@ extern const UnicodeString ContextSeparator;
 //---------------------------------------------------------------------------
 void __fastcall FixListColumnWidth(TListView * TListView, int Index);
 void __fastcall AutoSizeListColumnsWidth(TListView * ListView, int ColumnToShrinkIndex = -1);
+bool UseDarkModeForControl(TControl * Control);
 void __fastcall EnableControl(TControl* Control, bool Enable);
 void __fastcall ReadOnlyControl(TControl * Control, bool ReadOnly = true);
 void __fastcall ReadOnlyAndEnabledControl(TControl * Control, bool ReadOnly, bool Enabled);
@@ -25,6 +26,8 @@ void __fastcall UseSystemSettingsPre(TForm * Control);
 void __fastcall UseSystemSettingsPost(TForm * Control);
 void __fastcall UseSystemSettings(TForm * Control);
 void __fastcall ResetSystemSettings(TForm * Control);
+void UseDarkMode(TForm * Form);
+void ApplyColorMode(TForm * Form);
 void __fastcall LinkLabel(TStaticText * StaticText, UnicodeString Url = L"",
   TNotifyEvent OnEnter = NULL);
 void __fastcall LinkActionLabel(TStaticText * StaticText);