Is there a way to set the scrollbar color of ScrollablePanelControl and MultilineEditControl? #44
-
|
I cannot find any related configuration options. Have I overlooked anything? |
Beta Was this translation helpful? Give feedback.
Replies: 33 comments
-
|
Great question — and the answer was split between the two controls, but it's now consistent on
Controls.MultilineEdit()
.WithScrollbarColors(trackColor: Color.Grey23, thumbColor: Color.Cyan1)
.Build();
// or directly (raises INPC, so it plays nicely with binding):
editControl.ScrollbarColor = Color.Grey23; // track
editControl.ScrollbarThumbColor = Color.Cyan1; // thumb
Controls.ScrollablePanel()
.WithScrollbarColors(trackColor: Color.Grey23, thumbColor: Color.Cyan1)
.Build();
// or directly:
panel.ScrollbarColor = Color.Grey23; // track
panel.ScrollbarThumbColor = Color.Cyan1; // thumbBoth are This is on |
Beta Was this translation helpful? Give feedback.
-
|
@nickprotop Looks like you're still awake. My network is acting up right now, so I can't pull the GitHub repo. I'll run the tests later. Thanks for your work! |
Beta Was this translation helpful? Give feedback.
-
|
@nickprotop I think the scrollbar could use the border color by default for consistent visuals. |
Beta Was this translation helpful? Give feedback.
-
|
@changlv Two updates, both on 1. Your suggestion is the default. The scrollbar thumb now defaults to the border color, so it matches the control's frame out of the box — focused → active border, unfocused → inactive border. The track defaults to a dim grey ( 2. A correction to my earlier reply — and it's now actually true. I told you 3. Bonus: you can now set it theme-wide. The scrollbar colors are part of the theme, so instead of setting them per-control you can change them once for the whole app. Themes are mutable, so the direct way is: windowSystem.Theme.ScrollbarThumbColor = Color.Orange1; // applies app-wide on next repaintOr derive a named theme from a built-in one and register it (new var myTheme = Theme.From(new ModernGrayTheme())
.WithName("MyTheme")
.With(t =>
{
t.ScrollbarThumbColor = Color.Orange1; // focused thumb
t.ScrollbarThumbUnfocusedColor = Color.Grey50; // unfocused thumb
t.ScrollbarTrackColor = Color.Grey23; // track
})
.Build();
windowSystem.ThemeRegistryService.RegisterTheme("MyTheme", "My theme", () => myTheme);
windowSystem.ThemeStateService.SwitchTheme("MyTheme");Resolution order per control is: instance override ( All on |
Beta Was this translation helpful? Give feedback.
-
|
@nickprotop To be honest, when I tried to customize themes by implementing class DemoTheme : ITheme, I felt overwhelmed by the lengthy list of options. For now, I've created my own ThemeHelper and apply styles during the BuildUI process as a workaround. public static partial class ThemeHelper
{
public static readonly List<ThemeColors> Themes = [new ThemeColors(), new DarkThemeColors() ];
public static ThemeColors Current { get; set; } = Themes[1];
}
public class ThemeColors
{
public virtual string Name => "Light";
public virtual Color WhiteColor => ColorHelper.FromHex("#FFFFFF");
public virtual Color Gray100Color => ColorHelper.FromHex("#F8F9FA");
public virtual Color Gray200Color => ColorHelper.FromHex("#E9ECEF");
public virtual Color Gray300Color => ColorHelper.FromHex("#DEE2E6");
public virtual Color Gray400Color => ColorHelper.FromHex("#CED4DA");
public virtual Color Gray500Color => ColorHelper.FromHex("#ADB5BD");
public virtual Color Gray600Color => ColorHelper.FromHex("#6C757D");
public virtual Color Gray700Color => ColorHelper.FromHex("#495057");
public virtual Color Gray800Color => ColorHelper.FromHex("#343A40");
public virtual Color Gray900Color => ColorHelper.FromHex("#212529");
public virtual Color BlackColor => ColorHelper.FromHex("#000000");
public virtual Color BodyColor => BlackColor;
public virtual Color BodyBgColor => WhiteColor;
public virtual Color PrimaryColor => ColorHelper.FromHex("#0D6EFD");
public virtual Color SecondaryColor => Gray600Color;
public virtual Color TertiaryColor => SecondaryColor;
public virtual Color InfoColor => ColorHelper.FromHex("#0DCAF0");
public virtual Color SuccessColor => ColorHelper.FromHex("#198754");
public virtual Color WarningColor => ColorHelper.FromHex("#FFC107");
public virtual Color DangerColor => ColorHelper.FromHex("#DC3545");
public virtual Color LightColor => WhiteColor;
public virtual Color DarkColor => BlackColor;
public virtual Color PrimaryTextColor => PrimaryColor.ShadeRGB(0.6);
public virtual Color SecondaryTextColor => SecondaryColor.ShadeRGB(0.6);
public virtual Color TertiaryTextColor => SecondaryTextColor;
public virtual Color InfoTextColor => InfoColor.ShadeRGB(0.6);
public virtual Color SuccessTextColor => SuccessColor.ShadeRGB(0.6);
public virtual Color WarningTextColor => WarningColor.ShadeRGB(0.6);
public virtual Color DangerTextColor => DangerColor.ShadeRGB(0.6);
public virtual Color LightTextColor => WhiteColor;
public virtual Color DarkTextColor => BlackColor;
public virtual Color PrimaryBgColor => PrimaryColor.ShadeRGB(0.8);
public virtual Color SecondaryBgColor => SecondaryColor.ShadeRGB(0.8);
public virtual Color TertiaryBgColor => SecondaryBgColor;
public virtual Color InfoBgColor => InfoColor.ShadeRGB(0.8);
public virtual Color SuccessBgColor => SuccessColor.ShadeRGB(0.8);
public virtual Color WarningBgColor => WarningColor.ShadeRGB(0.8);
public virtual Color DangerBgColor => DangerColor.ShadeRGB(0.8);
public virtual Color LightBgColor => WhiteColor;
public virtual Color DarkBgColor => BlackColor;
public virtual Color TextOnPrimaryColor => WhiteColor;
public virtual Color TextOnSecondaryColor => Gray200Color;
public virtual Color TextOnTertiaryColor => Gray400Color;
public virtual Color TextOnInfoColor => WhiteColor;
public virtual Color TextOnSuccessColor => WhiteColor;
public virtual Color TextOnWarningColor => WhiteColor;
public virtual Color TextOnDangerColor => WhiteColor;
public virtual Color TextOnLightColor => BlackColor;
public virtual Color TextOnDarkColor => WhiteColor;
public virtual Color PrimaryBorderColor => PrimaryColor.TintRGB(0.6);
public virtual Color SecondaryBorderColor => SecondaryColor.TintRGB(0.6);
public virtual Color TertiaryBorderColor => SecondaryBorderColor;
public virtual Color InfoBorderColor => InfoColor.TintRGB(0.6);
public virtual Color SuccessBorderColor => InfoColor.TintRGB(0.6);
public virtual Color WarningBorderColor => WarningColor.TintRGB(0.6);
public virtual Color DangerBorderColor => DangerColor.TintRGB(0.6);
public virtual Color LightBorderColor => Gray200Color;
public virtual Color DarkBorderColor => Gray800Color;
}
public class DarkThemeColors : ThemeColors
{
public override string Name => "Dark";
public override Color BodyColor => Gray300Color;
public override Color BodyBgColor => Gray900Color;
public override Color SuccessColor => ColorHelper.FromHex("#6ECC54");
public override Color PrimaryTextColor => PrimaryColor.TintRGB(0.4);
public override Color SecondaryTextColor => SecondaryColor.TintRGB(0.4);
public override Color TertiaryTextColor => SecondaryTextColor;
public override Color InfoTextColor => InfoColor.TintRGB(0.4);
public override Color SuccessTextColor => SuccessColor.TintRGB(0.4);
public override Color WarningTextColor => WarningColor.TintRGB(0.4);
public override Color DangerTextColor => DangerColor.TintRGB(0.4);
public override Color LightTextColor => WhiteColor;
public override Color DarkTextColor => BlackColor;
public override Color PrimaryBgColor => PrimaryColor.ShadeRGB(0.8);
public override Color SecondaryBgColor => SecondaryColor.ShadeRGB(0.8);
public override Color TertiaryBgColor => SecondaryBgColor;
public override Color InfoBgColor => InfoColor.ShadeRGB(0.8);
public override Color SuccessBgColor => SuccessColor.ShadeRGB(0.8);
public override Color WarningBgColor => WarningColor.ShadeRGB(0.8);
public override Color DangerBgColor => DangerColor.ShadeRGB(0.8);
public override Color LightBgColor => WhiteColor;
public override Color DarkBgColor => BlackColor;
public override Color TextOnPrimaryColor => WhiteColor;
public override Color TextOnSecondaryColor => Gray200Color;
public override Color TextOnTertiaryColor => Gray400Color;
public override Color TextOnInfoColor => BlackColor;
public override Color TextOnSuccessColor => WhiteColor;
public override Color TextOnWarningColor => BlackColor;
public override Color TextOnDangerColor => WhiteColor;
public override Color TextOnLightColor => BlackColor;
public override Color TextOnDarkColor => WhiteColor;
public override Color PrimaryBorderColor => PrimaryColor.ShadeRGB(0.4);
public override Color SecondaryBorderColor => SecondaryColor.ShadeRGB(0.4);
public override Color TertiaryBorderColor => SecondaryBorderColor;
public override Color InfoBorderColor => InfoColor.ShadeRGB(0.4);
public override Color SuccessBorderColor => SuccessColor.ShadeRGB(0.4);
public override Color WarningBorderColor => WarningColor.ShadeRGB(0.4);
public override Color DangerBorderColor => DangerColor.ShadeRGB(0.4);
public override Color LightBorderColor => Gray700Color;
public override Color DarkBorderColor => Gray800Color;
}
public static partial class ColorHelper
{
public static Color FromHex(string hex)
{
if(Color.TryFromHex(hex, out var color)) return color;
throw new ArgumentException();
}
public static string WrapMarkup(this string text, Color textColor)
{
if (string.IsNullOrEmpty(text)) return text;
return $"[{textColor.ToMarkup()}]{Spectre.Console.AnsiMarkup.Escape(text)}[/]";
}
public static string WrapMarkup(this string text, Color textColor, Color bgColor)
{
if(string.IsNullOrEmpty(text)) return text;
return $"[{textColor.ToMarkup()} on {bgColor.ToMarkup()}]{Spectre.Console.AnsiMarkup.Escape(text)}[/]";
}
public static Color MixRGB(this Color color1, Color color2, double weight = 0.5, bool mixAlpha = false)
{
weight = Math.Clamp(weight, 0.0, 1.0);
double r = color1.R * (1 - weight) + color2.R * weight;
double g = color1.G * (1 - weight) + color2.G * weight;
double b = color1.B * (1 - weight) + color2.B * weight;
byte rb = (byte)Math.Round(r);
byte gb = (byte)Math.Round(g);
byte bb = (byte)Math.Round(b);
if (mixAlpha)
{
double a = color1.A * (1 - weight) + color2.A * weight;
byte ab = (byte)Math.Round(a);
return new Color(rb, gb, bb, ab);
}
return new Color(rb, gb, bb, color1.A);
}
public static Color WithAlpha(this Color color, double alpha)
{
alpha = Math.Clamp(alpha, 0.0, 1.0);
byte a = (byte)Math.Round(alpha * 255.0);
return new Color(color.R, color.G, color.B, a);
}
public static Color TintRGB(this Color color, double weight, bool withAlpha = false) => MixRGB(ColorHelper.FromHex("#FFFFFF"), color, weight, withAlpha);
public static Color ShadeRGB(this Color color, double weight, bool withAlpha = false) => MixRGB(ColorHelper.FromHex("#000000"), color, weight, withAlpha);
public static Color ShiftRGB(this Color color, double weight, bool withAlpha = false) => weight >= 0 ? ShadeRGB(color, weight, withAlpha) : TintRGB(color, -weight, withAlpha);
}The advantage of this approach is that secondary colors only need to be calculated. Basically, you only need to define the PrimaryColor to switch to an entirely new theme instantly. For example: public class MyThemeColors : DarkThemeColors
{
public override string Name => "MyDarkTheme";
public override Color PrimaryColor => ColorHelper.FromHex("#FD7E14");
} |
Beta Was this translation helpful? Give feedback.
-
|
@changlv That's a really nice approach — deriving a whole coherent theme from one color via tint/shade is much nicer than filling in a flat list. Thanks for sharing the code. Quick note first: you don't have to implement var myDark = Theme.From(new ModernGrayTheme())
.With(t => t.ActiveBorderForegroundColor = ColorHelper.FromHex("#FD7E14"))
.Build();But what you've actually built is better — a theme generator. So I'd like to add exactly that: a helper that takes one (or a few) seed colors and produces a ready theme, deriving everything else with tint/shade — your var theme = Theme.Generate(primary: Color.FromHex("#FD7E14"));
// change one color → whole consistent themeThe output is just a normal theme you register and switch to. Two questions to shape it:
|
Beta Was this translation helpful? Give feedback.
-
|
@nickprotop In fact, I’m not familiar with where every property of The current My approach follows the practice adopted by many popular modern UI frameworks. For example, to display an active window, you only need to brighten the primary color; for disabled states, darken it. You can also overlay transparency and similar adjustments. Nearly all visual scenarios can be covered with just 1 ~ 3 core colors this way. Furthermore, all your existing controls provide color configuration interfaces, supporting fine-grained My That’s roughly my idea. Hope my code can bring you some inspiration! |
Beta Was this translation helpful? Give feedback.
-
|
@nickprotop One additional point: I believe the anchor core colors are Also, standard color matching best practices state that a visual scheme should use no more than five core colors (preferably no more than three, excluding black, white and gray). |
Beta Was this translation helpful? Give feedback.
-
|
@changlv You nailed it — and it shipped. The generator you sketched is in on You were right about Your idea became var theme = Theme.FromPalette(new Palette
{
Primary = Color.FromHex("#0D6EFD"),
Background = Color.FromHex("#F8F9FA"),
});
windowSystem.ThemeRegistryService.RegisterTheme("MyTheme", "My theme", () => theme);
windowSystem.ThemeStateService.SwitchTheme("MyTheme");The anchors you called out — background, primary, secondary, tertiary — are all there, plus optional status colors. Anything you omit is derived: new Palette
{
Primary = ..., // accent: borders, focus, highlights
Secondary = ..., // optional
Tertiary = ..., // optional
Background = ..., // surface — everything else is tinted/shaded from it
// Success / Warning / Danger / Info optional
}Two things straight from your notes:
It also ships as built-in seed themes so every app gets a ready set out of the box (Ocean, Amber, Forest, Crimson, Slate, and a light Daylight), and your point about controls already exposing per-control One thing I'd value your eye on. You clearly have UI experience, and the part I'm least sure of is the distribution — which derived shade drives which control (which accent goes to selection vs. hover vs. focused border, how disabled states are damped, etc.). Does the mapping read right to you, or are there spots where a different anchor would look better? Your "≤3 core colors, brighten for active / darken for disabled" framing is exactly the lens I want on it. Build from |
Beta Was this translation helpful? Give feedback.
-
|
I think we should extract the behaviors and appearances that affect colors, as follows: Behaviors: Core appearances: Special-case appearances (used to replace primary colors in specific scenarios): Thus we can sort out the core colors: Special colors: For special status scenarios including Info, Success, Warning, Danger (Error) Others: Black, white and gray are essential. Looking forward to the new release! My network connection to github.com is extremely unstable. Pulling the project is unpredictable for me, which is really frustrating. |
Beta Was this translation helpful? Give feedback.
-
|
@changlv Your structure matches how the generator is built — behaviors (active/inactive, enabled/disabled, focus), core appearances (text / background / border+scrollbar), and the core colors (Primary, Secondary, Tertiary + Info/Success/Warning/Danger). What I'd still value your eye on is the concrete mapping — how those core colors actually get assigned to each control. For example, right now: Primary → active border / focused thumb; Secondary → selected-item background (e.g. So before I publish: does the mapping itself look right to you, or are there controls where you'd pull from a different core color? If it reads well, I'll ship the release; if not, let's adjust the mapping first. I can write out the full color→control table if that helps you judge it. |
Beta Was this translation helpful? Give feedback.
-
|
Windows and controls shall accept MainColor, [SecondaryColor], [BodyTextColor], [BodyBgColor]. MainColor include: Corresponding colors will be resolved based on the selected Color variants are calculated from the base Example: // --- Window --- //
Window.BodyBgColor = BodyBgColor;
Window.TextColor = BodyTextColor; // or MainTextColor;
Window.TitleTextColor = MainTextOnBackColor; // or MainTextColor
Window.TitleTextBgColor = MainBackColor; // or BodyBgColor
Window.TitleTextUnfocusColor = MainTextOnBackColor.ShadeRGB(); // or MainTextColor.ShadeRGB();
Window.TitleTextUnfocusBgColor = MainBackColor.ShadeRGB(); // or BodyBgColor.ShadeRGB();
Window.BorderColor = MainBorderColor;
Window.BorderUnfocusColor = MainBorderColor.ShadeRGB();
Window.ScrollColor = MainBorderColor;
Window.ScrollUnfocusColor =MainBorderColor.ShadeRGB();
// --- PrimaryButton --- //
PrimaryButton.TextColor = MainTextOnBackColor;
PrimaryButton.BgColor = MainBackColor;
PrimaryButton.BoderColor = MainBorderColor;
PrimaryButton.FocusTextColor =MainTextOnBackColor.InitRGB();
PrimaryButton.FocusBgColor =MainBackColor.InitRGB(); // or MainBackColor
PrimaryButton.FocusBoderColor = MainBorderColor;
PrimaryButton.DisableTextColor = MainTextOnBackColor.WithAlpha(); // or ShadeRGB()
PrimaryButton.DisableBgColor = MainBackColor.WithAlpha(); // or ShadeRGB()
PrimaryButton.DisableBoderColor = MainBorderColor;
// --- SecondaryButton --- //
SecondaryButton.TextColor = SecondaryTextOnBackColor;
SecondaryButton.BgColor = SecondaryBgColor;
SecondaryButton.BoderColor = SecondaryBorderColor;
SecondaryButton.FocusTextColor = SecondaryTextOnBackColor.InitRGB();
SecondaryButton.FocusBgColor =SecondaryBgColor.InitRGB(); // or SecondaryBgColor
SecondaryButton.FoucsBoderColor = SecondaryBorderColor;
SecondaryButton.DisableTextColor = SecondaryTextOnBackColor.WithAlpha(); // or ShadeRGB()
SecondaryButton.DisableBgColor = SecondaryBgColor.WithAlpha(); // or ShadeRGB()
SecondaryButton.DisableBoderColor = SecondaryBoderColor;
// --- OutlineButton (Drak Theme) --- //
OutlineButton.TextColor = MainTextColor;
OutlineButton.BgColor = BodyBgColor
OutlineButton.BorderColor = MainBorderColor;
OutlineButton.FocusTextColor =MainTextColor.InitRGB(); // or MainTextColor
OutlineButton.FocusBbColor = BodyBgColor;
OutlineButton.FocusBorderColor = MainBorderColor.InitRGB();
OutlineButton.DisableTextColor = MainTextOnBackColor.WithAlpha(); // or ShadeRGB()
OutlineButton.DisableBbColor = BodyBgColor;
OutlineButton.DisableBorderColor = MainBorderColor.WithAlpha(); // or ShadeRGB()
// --- NotifyMessage --- //
NotifyMessage.TextColor = MainTextColor;
NotifyMessage.BackColor = BodyBgColor;
// --- HeavyNotifyMessage --- //
HeavyNotifyMessage.TextColor = MainTextOnBackColor;
HeavyNotifyMessage.BackColor = MainBackColor;
// --- Dialog --- //
Dialog.BodyBgColor = BodyBgColor;
Dialog.TextColor = MainTextColor;
Dialog.TitleTextColor = MainTextOnBackColor; // or MainTextColor
Dialog.TitleTextBgColor = MainBackColor; // or BodyBgColor
Dialog.BorderColor = MainBorderColor;
Dialog.ScrollColor = MainBorderColor;Whether the visual effect looks good should be determined by the definition of core colors; the variants only need to have distinguishable differences visible to the naked eye. I wonder if the examples I provided have answered your questions ? |
Beta Was this translation helpful? Give feedback.
-
|
This was really helpful, changlv — thank you. Your "core colors drive everything" framing is what turned On your table: the good news is the generator already implements most of it. Let me map it against what's on Already matching your model (
On The one real gap — and it's a design decision, not a tweak: your That's the piece worth deciding on deliberately, because it's additive surface area across every control (a color-role concept), not a mapping change. I don't want to rush it into this release and get the API shape wrong. So: the scrollbar question that started this thread, plus the palette generator, are shipped and on |
Beta Was this translation helpful? Give feedback.
-
|
Quick follow-up, changlv — I tried your So it's now the default in the palette generator: disabled buttons, dropdowns, lists, checkboxes, tab headers, and date/time pickers all damp with alpha (~45% opacity) across every palette theme (Ocean, Slate, Daylight, …). One subtlety the contrast audit caught — once the disabled background goes translucent the window shows through it, so the disabled text now reads against the window background rather than the color it sat on when enabled. Keeps it readable on every theme. There's a new "Disabled States" page in the DemoApp (Controls group) that puts enabled vs. disabled side by side if you want to see it. Thanks again — that's two of your ideas in the generator now. |
Beta Was this translation helpful? Give feedback.
-
|
@nickprotop Regarding the section you mentioned about "The one real gap". I believe SharpConsoleUI, as a framework, should not dictate how users utilize it. A theme configuration acts as a theme color palette and provides users with a streamlined decision-making framework. Regarding the usage of Primary, Tertiary, Info, Success, Warning and Danger: For example, use outline buttons consistently in dark themes, warning dialogs, success notifications, error popups, log messages, and so on. In other words, Tertiary, Info, Success, Warning and Danger need to be set as the default color scheme for specific built-in components. To address your point about "I'm a Danger button": this styling should be determined by the built-in component's functional purpose. For instance, users can mark a Dialog as one of Primary / Info / Warning / Success / Danger by configuring: Dialog.ApplyMainColor = Warning; // default is PrimaryLikewise, users may set OutlineButton.BgColor = BodyBgColor;
OutlineButton.TextColor = DangerText;
OutlineButton.BorderColor = DangerBorder; Button.ApplyMainColor = Success; // Optional, default is Primary
Button.OutlineButton(true); // or Button.ReverseMainColor();,
// I believe `OutlineButton(true)` expresses the behavior more clearly than `ReverseMainColor()`.
Your responsibility is simply to provide users with this streamlined color palette. Ultimately, as long as we deliver polished out-of-the-box visual styles, users can customize the colors later to match their own requirements. I’m not sure if my explanation resolves your confusion. |
Beta Was this translation helpful? Give feedback.
-
|
@nickprotop You’ve understood my point, but the implementation approach may be overly rigid. I’ll provide a snippet of code for your reference. public class ThemeSettings
{
public Color Foreground { get; set; }
public Color Background { get; set; }
public Color Primary { get; set; }
public Color Secondary { get; set; }
public Color Tertiary { get; set; }
public Color Success { get; set; }
public Color Warning { get; set; }
public Color Danger { get; set; }
public Color Info { get; set; }
}
public enum ColorPaletteMode
{
Dark, // default in console app
Light
}
public class ColorPalette
{
public Color MainColor { get; private set; }
public Color TextColr { get; private set; }
public Color BackColor { get; private set; }
public Color BorderColor { get; private set; }
public Color DimTextColr { get; private set; }
public Color TextOnMainColor { get; private set; }
// other colors ...
public ColorPaletteMode Mode { get; private set; }
public ColorPalette(Color mainColor, Color? foreColor, Color? backColor, ColorPaletteMode mode = ColorPaletteMode.Dark)
{
MainColor = mainColor;
Mode = mode;
GenColors(mainColor, foreColor, backColor);
}
private void GenColors(Color mainColor, Color? foreColor, Color? backColor)
{
TextColr = foreColor ?? (Mode == ColorPaletteMode.Dark ? new Color(255, 255, 255) : new Color(0, 0, 0));
BackColor = backColor ?? (Mode == ColorPaletteMode.Dark ? new Color(0, 0, 0) : new Color(255, 255, 255));
BorderColor = Mode == ColorPaletteMode.Dark ? MainColor.MixRGB() : MainColor.MixRGB();
DimTextColr = Mode == ColorPaletteMode.Dark ? MainColor.MixRGB() : MainColor.MixRGB();
TextOnMainColor = Mode == ColorPaletteMode.Dark ? new Color(255, 255, 255) : new Color(0, 0, 0);
// other colors ...
}
}
public class ButtonBuilder
{
private Color? _backgroundColor;
private Color? _foregroundColor;
private Color? _focusedBackgroundColor;
private Color? _focusedForegroundColor;
private Color? _disabledBackgroundColor;
private Color? _disabledForegroundColor;
private Color? _borderColor;
private Color? _borderBackgroundColor;
private ColorPalette? colorPalette;
public void ApplyMainColor(Color mainColor)
{
colorPalette = new ColorPalette(Theme.Current.Settings.Primary, Theme.Current.Settings.Foreground, Theme.Current.Settings.Background);
_backgroundColor = colorPalette.BackColor;
_foregroundColor = colorPalette.TextColr;
_borderColor = colorPalette.BorderColor;
// set other colors .....
}
}Use example var dialog = new WindowBuilder(ws)
.ApplyMainColor(ThemeSettings.Warning)
.WithTitlt("Are you sure?")
.Build();
var btn_cancel = Controls.Button()
.ApplyMainColor(ThemeSettings.Secondary)
.WithTitlt("Cancel")
.Build();
var btn_ok = Controls.Button()
.ApplyMainColor(ThemeSettings.Primary)
.WithTitlt("OK")
.Build();
// custom main color
var btn_upgrade = Controls.Button()
.ApplyMainColor(Color("#6750A4"))
.WithTitlt("Upgrade")
.Build();
var btn_submit = Controls.Button()
.ApplyMainColor(ThemeSettings.Success)
.WithTitlt("Submit")
.Build();
var btn_Delete = Controls.Button()
.ApplyMainColor(ThemeSettings.Danger)
.WithTitlt("Delete")
.Build();
// custom palette
var palette = new ColorPalette("#0DCAF0");
var text = Controls.Markup()
.WithBackgroundColor(palette.MainColor)
.WithForegroundColor(palette.TextOnMainColor)
.Build();The code above is not rigorous, but the core idea is that every control gets access to a ColorPalette and retrieves colors from it as needed. Limiting roles would be overly rigid.
|
Beta Was this translation helpful? Give feedback.
-
|
Thanks — I think we're closer than it looks: your Your example shows two things, and I want to solve the right one:
The catch with (2): a hardcoded If (2) is the ask, it's a clean addition: an overload — Same engine either way — it's just whether the door is the 7 roles, any color, or both. Which fits how you'd actually use it? |
Beta Was this translation helpful? Give feedback.
-
|
@nickprotop I believe Role defines colors based on behavior, while ApplyMainColor lets users decide what behavior a given color represents. I personally prefer the ApplyMainColor approach, as this is something to be determined at the application layer (user code). Additionally, I have revised the earlier examples. |
Beta Was this translation helpful? Give feedback.
-
|
@nickprotop Users shall take full responsibility for any custom colors they define. |
Beta Was this translation helpful? Give feedback.
-
|
@nickprotop I suddenly realize why you’ve been stuck on this, and why ITheme has over a hundred properties. You’re treating SharpConsoleUI as a complete application and trying to handle everything internally, but SharpConsoleUI is meant to be a framework. Don’t forget there’s an application layer (user code) built on top of it. |
Beta Was this translation helpful? Give feedback.
-
|
I think we actually agree more than it looks — and honestly, your roles + generator idea moved this framework forward. Let me reframe with that in mind. Take WinUI, which I know best: it does declare every color of every control at the low level (the theme resource dictionaries) — and frankly that low level was chaos. Thousands of brush keys, schemes layered on schemes; powerful, but a swamp to work in. A framework needs that completeness — it's how a control knows what to paint — but raw, it's miserable. What your roles and
// whole theme (~130 colors) from 2 seeds — your idea:
var ocean = Theme.FromPalette(new Palette {
Primary = Color.FromHex("#2DD4BF"),
Background = Color.FromHex("#0B1F2A"),
});
// per control, one method:
new ButtonBuilder().WithText("Delete").WithRole(ControlRole.Danger).Build();So the ~130 And the app can still control everything, exactly like copying a WinUI resource XAML and editing it to the extreme — declare or derive a full theme ( Where you're plainly right is the middle escape hatch: theme a control to its own color, not just the 7 roles — your |
Beta Was this translation helpful? Give feedback.
-
|
@nickprotop Alright, as long as it achieves the desired effect. Also, does the ApplyMainColor method need to take a ThemeMode parameter? like: |
Beta Was this translation helpful? Give feedback.
-
|
@nickprotop Maybe we can rename |
Beta Was this translation helpful? Give feedback.
-
|
@nickprotop We can either choose ApplyMainColor or WithRole. Having too many overloaded methods with similar functionality but different names is confusing. |
Beta Was this translation helpful? Give feedback.
-
|
Essentially, the system does not require as many colors as one might think. A Cell fundamentally only needs a foreground color and a background color, which gives us: However, both Text and Border elements use ForeColor, so we need to distinguish them, resulting in: Next, we add variants based on control states: Normal, Focused, Disabled, yielding: BorderColor BackColor Finally, we introduce MainColor plus a special case TextOnMainColor. The complete color palette is as follows: MainColor TextColor BorderColor BackColor TextOnMainColor Simply retrieve the corresponding color based on the type of displayed content and the current control state. There is no need to define an excessive number of colors at the low level. |
Beta Was this translation helpful? Give feedback.
-
|
Good points — and easy call. First, a small clarification: there's no On the name: you're right that "Role" carries baggage (permissions/user-roles). I first tried button.WithColorRole(ColorRole.Danger);
button.WithColorRole(ColorRole.Success, ThemeMode.Dark); // pin dark/light derivation
button.WithColorRole(ColorRole.Warning); // mode omitted → inferred from the active themeOne method, On the mode: good call — you keep the control. Pass a Renamed the prerelease |
Beta Was this translation helpful? Give feedback.
-
|
@nickprotop If the WithColorRole method respects the active theme, the ThemeMode parameter becomes unnecessary. |
Beta Was this translation helpful? Give feedback.
-
|
Two things: On On the color model — you're right, and that's actually how the resolver already works. Your The one place I'd refine your model: in a real compositor, deriving against a dark/light flag is coarser than deriving against the actual surface color. The resolver does the latter — it keeps the text/border at real contrast against the specific background they land on (luminance-checked), not just "dark mode → white text." That's stronger than a fixed So the ~130 |
Beta Was this translation helpful? Give feedback.
-
|
@nickprotop Alright, I have no further questions. Looking forward to the new preview release! |
Beta Was this translation helpful? Give feedback.
-
|
Thanks, changlv — your ideas genuinely helped. |
Beta Was this translation helpful? Give feedback.
Great question — and the answer was split between the two controls, but it's now consistent on
master:MultilineEditControl— already supported. Set the track and thumb colors via the builder or the properties:ScrollablePanelControl— you weren't missing anything; it genuinely had no option. Its scrollbar colors were hardcoded. I've just added the matching API (commit4a1a880, onmaster) so the two…