Skip to content

Commit

Permalink
ButtonStrip UC: implement bulk of new theming features
Browse files Browse the repository at this point in the history
The trusty horizontal "ButtonStrip" control has been the testbed for
PD's capstone visual themes feature: determining UI colors from a
standalone XML file, instead of hard-coding into the program.
(Eventually, failsafes will be added to ensure said XML file exists and
is valid, etc, but for now, do not do weird things like delete PD's
/Themes subfolder).

This is a cumbersome project because I need to rework most controls to
optimize how theme-specific colors are retrieved and applied.  XML
lookups are obviously slow inside of rendering code, so a new class -
pdThemeColors - is used by each control to cache whatever color values
they require.  Inside the rendering loop, that class returns needed
colors very quickly, without having to touch their original XML
definitions.  Similarly, if PD's visual theme is live-changed, the
pdThemeColors class will handle caching the new colors, sparing the
control from that kind of monotonous work.

Similarly, a lot of PD's controls currently shortcut parts of the
rendering process because the current "theme" decisions don't always
require (for example) separate border and fill colors.  In an ideal
world, however, these colors *could* be defined inside a theme file, and
PD should automatically pick up the new definitions and render
accordingly.

Finally, the actual theme file has to be slowly assembled according to
the needs of each individual PD control, and color definitions have to
be moved out of the code and into XML format.  This is a tedious
process, but a necessary one for easily supporting dark themes (and
other accessibility-friendly variants).

There's still some clean-up to do on the buttonStrip implementation, but
so far it's proven to be a solid testbed for finding weaknesses and
problems in PD's existing theming code.  Once I'm confident that the new
theming classes are all working as they should, I'll start converting
PD's other custom controls to match.
  • Loading branch information
tannerhelland committed Jan 21, 2016
1 parent 1888c36 commit b873321
Show file tree
Hide file tree
Showing 9 changed files with 282 additions and 125 deletions.
55 changes: 45 additions & 10 deletions App/PhotoDemon/Themes/Default.xml
Expand Up @@ -9,9 +9,16 @@
<!-- The Definitions section describes any custom, reusable color values you wish to create. -->

<Definitions>
<BlackTest>#000000</BlackTest>
<BlueTest>#0000ff</BlueTest>
<WhiteTest>#ffffff</WhiteTest>
<AccentDefault>#3296dc</AccentDefault>
<AccentShadow>#3270dc</AccentShadow>
<AccentHighlight>#3cafe6</AccentHighlight>
<AccentUltralight>#d2f0fa</AccentUltralight>
<DefaultGray>#929292</DefaultGray>
<DefaultBackground>#ffffff</DefaultBackground>
<DisabledGray>#b1bac2</DisabledGray>
<TextDefault>#404040</TextDefault>
<TextHyperlink>#3170c0</TextHyperlink>
<TextInvert>#ffffff</TextInvert>
</Definitions>

<!-- The Colors section contains a hard-coded list of colors used by PD. -->
Expand All @@ -20,21 +27,49 @@

<!-- Default colors are not object-specific. They are used as fallback values if an object lacks a given color name. -->
<Default>
<Background>#aaaaaa</Background>
<Background>DefaultBackground</Background>
</Default>

<!-- All colors past this point are organized by object name. Missing values will be taken from the Default section. -->

<!-- ****************************************************************************************** -->
<!-- PD's "Button Strip" control -->

<ButtonStrip>
<Background></Background>
<ActiveItemFill>#0000aa</ActiveItemFill>
<InactiveItemFill>WhiteTest</InactiveItemFill>
<ActiveItemBorder>BlueTest</ActiveItemBorder>
<InactiveItemBorder>BlueTest</InactiveItemBorder>
<ActiveText>WhiteTest</ActiveText>
<InactiveText>BlackTest</InactiveText>
<SelectedItemFill>AccentDefault</SelectedItemFill>
<SelectedItemBorder>AccentShadow</SelectedItemBorder>
<SelectedText>TextInvert</SelectedText>
<UnselectedItemFill>DefaultBackground</UnselectedItemFill>
<UnselectedItemBorder>DefaultGray</UnselectedItemBorder>
<UnselectedItemBorder-hover>AccentDefault</UnselectedItemBorder-hover>
<UnselectedText>TextDefault</UnselectedText>
<UnselectedText-hover>TextHyperlink</UnselectedText-hover>
<SelectedItemFill-disabled>DisabledGray</SelectedItemFill-disabled>
<SelectedItemBorder-disabled>DisabledGray</SelectedItemBorder-disabled>
<UnselectedItemBorder-disabled>DisabledGray</UnselectedItemBorder-disabled>
<SelectedText-disabled>DefaultBackground</SelectedText-disabled>
<UnselectedText-disabled>DisabledGray</UnselectedText-disabled>

<!-- On the Text and Typography tool panels, the button strip uses a "lighter" color scheme for the text justification options. The control internally defines this as "light mode" and it requires special colors. -->
<BackgroundLightMode>DefaultBackground</BackgroundLightMode>
<SelectedItemFillLightMode>AccentUltralight</SelectedItemFillLightMode>
<SelectedItemBorderLightMode>AccentHighlight</SelectedItemBorderLightMode>
<SelectedItemBorderLightMode-hover>AccentDefault</SelectedItemBorderLightMode-hover>
<SelectedTextLightMode>TextDefault</SelectedTextLightMode>
<UnselectedItemFillLightMode>DefaultBackground</UnselectedItemFillLightMode>
<UnselectedItemBorderLightMode>DefaultBackground</UnselectedItemBorderLightMode>
<UnselectedItemBorderLightMode-hover>AccentDefault</UnselectedItemBorderLightMode-hover>
<UnselectedTextLightMode>TextDefault</UnselectedTextLightMode>
<SelectedItemFillLightMode-disabled>DisabledGray</SelectedItemFillLightMode-disabled>
<SelectedItemBorderLightMode-disabled>DisabledGray</SelectedItemBorderLightMode-disabled>
<UnselectedItemBorderLightMode-disabled>DisabledGray</UnselectedItemBorderLightMode-disabled>
<SelectedTextLightMode-disabled>DefaultBackground</SelectedTextLightMode-disabled>
<UnselectedTextLightMode-disabled>DisabledGray</UnselectedTextLightMode-disabled>
</ButtonStrip>

<!-- End "Button Strip" -->
<!-- ****************************************************************************************** -->

</Colors>

Expand Down
42 changes: 37 additions & 5 deletions Classes/pdThemeColors.cls
Expand Up @@ -15,8 +15,8 @@ Attribute VB_Exposed = False
'Visual Theme Color List class
'Copyright 2016-2016 by Tanner Helland
'Created: 15/January/16
'Last updated: 15/January/16
'Last update: initial build
'Last updated: 21/January/16
'Last update: continue fleshing out features
'
'Each individual PD control uses a unique list of colors. Some of these colors may be modified by different settings
' or actions (e.g. enabled vs disabled, hovered vs active).
Expand All @@ -42,7 +42,7 @@ Private m_NumOfColors As Long
'Color definition. If a variation is missing, it will be replaced by the BaseColor value. (As such, the BaseColor
' value must always be present in a color definition.)
Private Type pdThemeColor
BaseColor As Long
baseColor As Long
DisabledColor As Long
ActiveColor As Long
HoverColor As Long
Expand Down Expand Up @@ -119,7 +119,7 @@ Public Function LoadThemeColor(ByVal colorIndex As Long, ByVal colorName As Stri
If colorLookupSuccessful Then

'Store the base color value. We'll use this as the basis for all subsequent color calculations.
m_ColorList(colorIndex).BaseColor = baseColorValue
m_ColorList(colorIndex).baseColor = baseColorValue

'Because this color successfully exists, we are now going to check for disabled, active, hovered,
' and active+hovered variants. Each must be handled individually, and all are optional. (If an
Expand All @@ -146,7 +146,7 @@ Public Function LoadThemeColor(ByVal colorIndex As Long, ByVal colorName As Stri
colorLookupSuccessful = Colors.GetColorFromString(IDE_FailsafeColor, baseColorValue)
If colorLookupSuccessful Then
With m_ColorList(colorIndex)
.BaseColor = baseColorValue
.baseColor = baseColorValue
.DisabledColor = baseColorValue
.ActiveColor = baseColorValue
.HoverColor = baseColorValue
Expand Down Expand Up @@ -186,3 +186,35 @@ Private Sub FillInColorVariant(ByRef objectName As String, ByRef colorName As St
End If

End Sub

'Once all colors have been populated, this function is used to return actual color values. It's all lookup-table based,
' so feel free to use it inside actual rendering functions.
Public Function RetrieveColor(ByVal colorIndex As Long, Optional ByVal enabledState As Boolean = True, Optional ByVal activeState As Boolean = False, Optional ByVal hoverState As Boolean = False) As Long

'Before doing anything else, validate the color index
If (colorIndex >= 0) And (colorIndex < m_NumOfColors) Then

'Branch according to the passed control state. The combination of values determines which color we return.
If enabledState Then
If activeState Then
If hoverState Then
RetrieveColor = m_ColorList(colorIndex).ActiveHoverColor
Else
RetrieveColor = m_ColorList(colorIndex).ActiveColor
End If
Else
If hoverState Then
RetrieveColor = m_ColorList(colorIndex).HoverColor
Else
RetrieveColor = m_ColorList(colorIndex).baseColor
End If
End If
Else
RetrieveColor = m_ColorList(colorIndex).DisabledColor
End If

Else
Debug.Print "WARNING! You've requested an invalid color index from pdThemeColors.RetrieveColor()!"
End If

End Function
30 changes: 26 additions & 4 deletions Classes/pdVisualThemes.cls
Expand Up @@ -151,6 +151,12 @@ Private Sub LoadDefaultPDTheme()
Dim themeLoadedCorrectly As Boolean
themeLoadedCorrectly = Me.LoadThemeFile(themePath)

#If DEBUGMODE = 1 Then
If Not themeLoadedCorrectly Then
If g_IsProgramRunning Then pdDebug.LogAction "WARNING! Failed to load theme file: " & themePath
End If
#End If

'Theme colors are loaded on-demand, so we have no further work to do here

'OLD CODE FOLLOWS:
Expand Down Expand Up @@ -179,6 +185,9 @@ Public Function LoadThemeFile(ByVal themePath As String) As Boolean
'If the user's choice of theme didn't load correctly, or the default theme failed to load, run some heuristics
' on the theme folder.
If Not LoadThemeFile Then
#If DEBUGMODE = 1 Then
If g_IsProgramRunning Then pdDebug.LogAction "WARNING! PD's default theme failed to load!"
#End If
' (TODO: this entire step, including pulling themes from the .exe's resource section as necessary)
End If

Expand Down Expand Up @@ -315,31 +324,44 @@ End Function
' to the Default namespace as necessary. Also, colors described by definition will automatically be tracked back to their
' source. (Note, however, that this function has no way to deal with circular references, so please avoid that.)
' RETURNS: a color hexadecimal value if successful; a null-string otherwise.
Public Function LookUpColor(ByRef objectName As String, ByRef colorName As String) As String
Public Function LookUpColor(ByVal objectName As String, ByRef colorName As String) As String

'First things first: see if the object name exists in the theme file. If it doesn't, we need to fall back to the
' "default" namespace.
Const DEFAULT_NAMESPACE As String = "Default"
Dim objectNameExists As Boolean
objectNameExists = m_XML.doesTagExist(objectName)
If Not objectNameExists Then
objectName = "Default"
objectName = DEFAULT_NAMESPACE
objectNameExists = m_XML.doesTagExist(objectName)
End If

'If the color exists in either the Default or object-specific namespace, we can proceed with parsing.
If objectNameExists Then

'Inside the current object's color definition block, retrieve the specified color
Dim colorDescription As String
Dim colorDescription As String, finalColor As String
colorDescription = m_XML.getUniqueTag_String(colorName, vbNullString, , objectName)

'If we retrieved any valid string, attempt to resolve it to an actual color value. (At this point, the color
' may just be a variable instead of an actual hex value.)
If Len(colorDescription) <> 0 Then
Dim finalColor As String
finalColor = ResolveColor(colorDescription)

'If we used a custom object name, but no color is defined for that value, try a new retrieval from
' the "Default" namespace. (Empty colors are still valid, as long as their Default variant is defined.)
Else
If StrComp(objectName, DEFAULT_NAMESPACE, vbBinaryCompare) <> 0 Then
objectName = DEFAULT_NAMESPACE
If m_XML.doesTagExist(objectName) Then
colorDescription = m_XML.getUniqueTag_String(colorName, vbNullString, , objectName)
If Len(colorDescription) <> 0 Then finalColor = ResolveColor(colorDescription)
End If
End If
End If

LookUpColor = finalColor

Else
LookUpColor = vbNullString
End If
Expand Down

0 comments on commit b873321

Please sign in to comment.