Files
Gromov_IA 8bc1583805 Прошивка моста BL616 для Tang Primer 25K и программатор TangProgrammer
- src/: прошивка встроенного отладчика BL616 — составное USB-устройство
  1209:C0CA: DirtyJTAG v3 (WinUSB через MS OS 2.0) + CDC-ACM UART к FPGA
  B3/C3; UART до 20 Мбод на DMA, JTAG до 30 МГц через аппаратный SPI;
- bin/: загрузчик Sipeed (0x0) и штатная прошивка 2025030317 для отката;
- gui/: TangProgrammer.exe (C# WinForms, .NET Framework 4) — загрузка FPGA
  в SRAM и во флеш-память, копия и стирание флеш-памяти FPGA, прошивка
  моста; светлая и тёмная тема;
- scripts/: setup, build, flash, build_gui, make_portable, а также
  make_release и publish_release для релизов Gitea;
- fpga_test/, tests/: тестовые проекты Gowin (loopback, stream) и тесты
  UART на Python;
- tools/openFPGALoader/: openFPGALoader 1.1.1 для Windows x64.
2026-10-02 14:59:10 +03:00

794 lines
28 KiB
C#

// Light/dark palette and the owner-drawn controls of the programmer GUI.
// Stock WinForms controls (tabs, buttons, check boxes, progress bar) are
// drawn by the light Windows theme only, so these paint themselves.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Microsoft.Win32;
namespace TangProgrammer
{
enum Tone { Text, Dim, Ok, Err, Warn, Title }
sealed class Theme
{
public bool IsDark;
public Color Window, Card, CardBorder, LogBg;
public Color Text, TextDim, TextDisabled;
public Color Button, ButtonHover, ButtonPressed, ButtonBorder, ButtonDisabled;
public Color Accent, AccentHover, AccentPressed, AccentText, AccentDisabled, AccentTextDisabled;
public Color ToggleFill, ToggleBorder;
public Color Input, Track;
public Color Ok, Err, Warn, Title;
public Color DotOn, DotWarn, DotOff;
public Color MenuBg, MenuHover, MenuBorder;
public static readonly Theme Light = new Theme
{
Window = Rgb(0xF3F3F3), Card = Rgb(0xFFFFFF), CardBorder = Rgb(0xE3E3E3), LogBg = Rgb(0xFFFFFF),
Text = Rgb(0x1B1B1B), TextDim = Rgb(0x626262), TextDisabled = Rgb(0xA3A3A3),
Button = Rgb(0xFBFBFB), ButtonHover = Rgb(0xF3F3F3), ButtonPressed = Rgb(0xEBEBEB),
ButtonBorder = Rgb(0xD3D3D3), ButtonDisabled = Rgb(0xF5F5F5),
Accent = Rgb(0x0067C0), AccentHover = Rgb(0x1975C5), AccentPressed = Rgb(0x3183CA),
AccentText = Rgb(0xFFFFFF), AccentDisabled = Rgb(0xC5C5C5), AccentTextDisabled = Rgb(0xFFFFFF),
ToggleFill = Rgb(0xFAFAFA), ToggleBorder = Rgb(0x8A8A8A),
Input = Rgb(0xFFFFFF), Track = Rgb(0xE0E0E0),
Ok = Rgb(0x0F7B0F), Err = Rgb(0xC42B1C), Warn = Rgb(0x9D5D00), Title = Rgb(0x005FB8),
DotOn = Rgb(0x13A10E), DotWarn = Rgb(0xE8911A), DotOff = Rgb(0xA0A0A0),
MenuBg = Rgb(0xFFFFFF), MenuHover = Rgb(0xEDEDED), MenuBorder = Rgb(0xD5D5D5),
};
public static readonly Theme Dark = new Theme
{
IsDark = true,
Window = Rgb(0x202020), Card = Rgb(0x2B2B2B), CardBorder = Rgb(0x3A3A3A), LogBg = Rgb(0x252525),
Text = Rgb(0xEDEDED), TextDim = Rgb(0xA6A6A6), TextDisabled = Rgb(0x6E6E6E),
Button = Rgb(0x373737), ButtonHover = Rgb(0x3E3E3E), ButtonPressed = Rgb(0x333333),
ButtonBorder = Rgb(0x484848), ButtonDisabled = Rgb(0x313131),
Accent = Rgb(0x4CC2FF), AccentHover = Rgb(0x48B1E8), AccentPressed = Rgb(0x42A1D2),
AccentText = Rgb(0x000000), AccentDisabled = Rgb(0x454545), AccentTextDisabled = Rgb(0x9A9A9A),
ToggleFill = Rgb(0x262626), ToggleBorder = Rgb(0x9E9E9E),
Input = Rgb(0x383838), Track = Rgb(0x474747),
Ok = Rgb(0x6CCB5F), Err = Rgb(0xFF99A4), Warn = Rgb(0xFCE100), Title = Rgb(0x60CDFF),
DotOn = Rgb(0x6CCB5F), DotWarn = Rgb(0xF7B955), DotOff = Rgb(0x6E6E6E),
MenuBg = Rgb(0x2C2C2C), MenuHover = Rgb(0x3D3D3D), MenuBorder = Rgb(0x484848),
};
public static Theme Current = Light;
static Color Rgb(int rgb)
{
return Color.FromArgb((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF);
}
public Color Of(Tone tone)
{
switch (tone)
{
case Tone.Dim: return TextDim;
case Tone.Ok: return Ok;
case Tone.Err: return Err;
case Tone.Warn: return Warn;
case Tone.Title: return Title;
default: return Text;
}
}
// "Choose your app mode" in the Windows settings
public static bool SystemPrefersDark()
{
try
{
using (RegistryKey k = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"))
{
object v = k == null ? null : k.GetValue("AppsUseLightTheme");
return v is int && (int)v == 0;
}
}
catch (Exception)
{
return false;
}
}
}
static class Ui
{
public static float Scale = 1f;
public static int S(float px)
{
return (int)Math.Round(px * Scale);
}
public static GraphicsPath Round(RectangleF r, float radius)
{
var p = new GraphicsPath();
float d = Math.Min(radius * 2, Math.Min(r.Width, r.Height));
if (d < 1f)
{
p.AddRectangle(r);
return p;
}
p.AddArc(r.X, r.Y, d, d, 180, 90);
p.AddArc(r.Right - d, r.Y, d, d, 270, 90);
p.AddArc(r.Right - d, r.Bottom - d, d, d, 0, 90);
p.AddArc(r.X, r.Bottom - d, d, d, 90, 90);
p.CloseFigure();
return p;
}
public static Color ParentBack(Control c)
{
return c.Parent != null ? c.Parent.BackColor : c.BackColor;
}
[DllImport("uxtheme.dll", CharSet = CharSet.Unicode)]
static extern int SetWindowTheme(IntPtr hwnd, string subAppName, string subIdList);
[DllImport("dwmapi.dll")]
static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int value, int size);
[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam);
// dark scroll bars and combo boxes of native controls (Windows 10 1809+);
// null restores the default look
public static void SetNativeTheme(Control c, string themeClass)
{
SetWindowTheme(c.Handle, themeClass, null);
c.Invalidate();
}
public static void SetDarkTitleBar(Form f, bool dark)
{
int v = dark ? 1 : 0;
// DWMWA_USE_IMMERSIVE_DARK_MODE: 20, was 19 before Windows 10 20H1
if (DwmSetWindowAttribute(f.Handle, 20, ref v, 4) != 0)
DwmSetWindowAttribute(f.Handle, 19, ref v, 4);
}
public static void SetRedraw(Control c, bool on)
{
SendMessage(c.Handle, 0x000B /* WM_SETREDRAW */, (IntPtr)(on ? 1 : 0), IntPtr.Zero);
if (on)
c.Invalidate();
}
}
// White (or dark) rounded panel with a thin border
sealed class Card : Panel
{
public Color Border = Color.Gray;
public Card()
{
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true);
}
protected override void OnPaintBackground(PaintEventArgs e)
{
Graphics g = e.Graphics;
g.Clear(Ui.ParentBack(this));
g.SmoothingMode = SmoothingMode.AntiAlias;
using (GraphicsPath p = Ui.Round(new RectangleF(0.5f, 0.5f, Width - 1f, Height - 1f), Ui.S(7)))
using (var fill = new SolidBrush(BackColor))
using (var pen = new Pen(Border))
{
g.FillPath(fill, p);
g.DrawPath(pen, p);
}
}
}
// Text tabs with an accent underline (replaces the TabControl, which can't be dark)
sealed class TabStrip : Control
{
readonly List<string> items = new List<string>();
int selected, hot = -1;
Font bold;
public event EventHandler SelectedIndexChanged;
public TabStrip()
{
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true);
SetStyle(ControlStyles.Selectable, false);
}
public void Add(string text)
{
items.Add(text);
Invalidate();
}
public int SelectedIndex
{
get { return selected; }
set
{
if (value == selected || value < 0 || value >= items.Count)
return;
selected = value;
Invalidate();
if (SelectedIndexChanged != null)
SelectedIndexChanged(this, EventArgs.Empty);
}
}
public int Count
{
get { return items.Count; }
}
Font Bold
{
get
{
if (bold == null || bold.Size != Font.Size)
bold = new Font("Segoe UI Semibold", Font.Size);
return bold;
}
}
int ItemWidth(int i)
{
return TextRenderer.MeasureText(items[i], Bold).Width + Ui.S(24);
}
Rectangle ItemRect(int i)
{
int x = 0;
for (int k = 0; k < i; k++)
x += ItemWidth(k);
return new Rectangle(x, 0, ItemWidth(i), Height);
}
int HitTest(Point p)
{
for (int i = 0; i < items.Count; i++)
if (ItemRect(i).Contains(p))
return i;
return -1;
}
public override Size GetPreferredSize(Size proposed)
{
int w = 0;
for (int i = 0; i < items.Count; i++)
w += ItemWidth(i);
return new Size(w, Font.Height + Ui.S(16));
}
protected override void OnPaintBackground(PaintEventArgs e)
{
}
protected override void OnPaint(PaintEventArgs e)
{
Theme th = Theme.Current;
Graphics g = e.Graphics;
g.Clear(Ui.ParentBack(this));
int line = Ui.S(3);
for (int i = 0; i < items.Count; i++)
{
Rectangle r = ItemRect(i);
bool sel = i == selected;
Color c = sel || i == hot ? th.Text : th.TextDim;
TextRenderer.DrawText(g, items[i], sel ? Bold : Font, new Rectangle(r.X, 0, r.Width, r.Height - line),
c, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter |
TextFormatFlags.SingleLine | TextFormatFlags.NoPrefix);
if (sel)
{
int w = TextRenderer.MeasureText(items[i], Bold).Width - Ui.S(6);
var u = new RectangleF(r.X + (r.Width - w) / 2f, r.Bottom - line - Ui.S(1), w, line);
g.SmoothingMode = SmoothingMode.AntiAlias;
using (GraphicsPath p = Ui.Round(u, line / 2f))
using (var b = new SolidBrush(th.Accent))
g.FillPath(b, p);
g.SmoothingMode = SmoothingMode.None;
}
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
int h = HitTest(e.Location);
if (h != hot)
{
hot = h;
Cursor = h >= 0 && h != selected ? Cursors.Hand : Cursors.Default;
Invalidate();
}
base.OnMouseMove(e);
}
protected override void OnMouseLeave(EventArgs e)
{
hot = -1;
Invalidate();
base.OnMouseLeave(e);
}
protected override void OnMouseDown(MouseEventArgs e)
{
int h = HitTest(e.Location);
if (h >= 0 && e.Button == MouseButtons.Left)
{
SelectedIndex = h;
Cursor = Cursors.Default;
}
base.OnMouseDown(e);
}
protected override void Dispose(bool disposing)
{
if (disposing && bold != null)
bold.Dispose();
base.Dispose(disposing);
}
}
enum Glyph { None, Contrast }
// Rounded button in the colors of the current theme; Accent = the main action
sealed class FlatButton : Button
{
public bool Accent;
public Glyph Glyph;
bool hover, pressed;
public FlatButton()
{
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true);
FlatStyle = FlatStyle.Flat;
FlatAppearance.BorderSize = 0;
UseVisualStyleBackColor = false;
}
public override Size GetPreferredSize(Size proposed)
{
Size t = TextRenderer.MeasureText(Text, Font, Size.Empty, TextFormatFlags.SingleLine | TextFormatFlags.NoPrefix);
return new Size(Math.Max(MinimumSize.Width, t.Width + Padding.Horizontal + Ui.S(8)),
Math.Max(MinimumSize.Height, t.Height + Padding.Vertical + Ui.S(6)));
}
protected override void OnMouseEnter(EventArgs e)
{
hover = true;
Invalidate();
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(EventArgs e)
{
hover = pressed = false;
Invalidate();
base.OnMouseLeave(e);
}
protected override void OnMouseDown(MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
pressed = true;
Invalidate();
}
base.OnMouseDown(e);
}
protected override void OnMouseUp(MouseEventArgs e)
{
pressed = false;
Invalidate();
base.OnMouseUp(e);
}
protected override void OnEnabledChanged(EventArgs e)
{
hover = pressed = false;
Invalidate();
base.OnEnabledChanged(e);
}
protected override void OnPaintBackground(PaintEventArgs e)
{
}
protected override void OnPaint(PaintEventArgs e)
{
Theme th = Theme.Current;
Graphics g = e.Graphics;
g.Clear(Ui.ParentBack(this));
Color bg, fg, border;
if (Accent)
{
bg = !Enabled ? th.AccentDisabled : pressed ? th.AccentPressed : hover ? th.AccentHover : th.Accent;
fg = Enabled ? th.AccentText : th.AccentTextDisabled;
border = bg;
}
else
{
bg = !Enabled ? th.ButtonDisabled : pressed ? th.ButtonPressed : hover ? th.ButtonHover : th.Button;
fg = Enabled ? th.Text : th.TextDisabled;
border = th.ButtonBorder;
}
g.SmoothingMode = SmoothingMode.AntiAlias;
using (GraphicsPath p = Ui.Round(new RectangleF(0.5f, 0.5f, Width - 1f, Height - 1f), Ui.S(4)))
using (var b = new SolidBrush(bg))
using (var pen = new Pen(border))
{
g.FillPath(b, p);
g.DrawPath(pen, p);
}
if (Focused && ShowFocusCues)
{
using (GraphicsPath p = Ui.Round(new RectangleF(2.5f, 2.5f, Width - 5f, Height - 5f), Ui.S(3)))
using (var pen = new Pen(fg))
g.DrawPath(pen, p);
}
if (Glyph == Glyph.Contrast)
{
float d = Math.Min(Width, Height) * 0.46f;
var r = new RectangleF((Width - d) / 2f, (Height - d) / 2f, d, d);
using (var b = new SolidBrush(fg))
g.FillPie(b, r.X, r.Y, r.Width, r.Height, 90, 180);
using (var pen = new Pen(fg, Math.Max(1.2f, Ui.Scale * 1.3f)))
g.DrawEllipse(pen, r);
return;
}
g.SmoothingMode = SmoothingMode.None;
TextRenderer.DrawText(g, Text, Font, ClientRectangle, fg,
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter |
TextFormatFlags.SingleLine | TextFormatFlags.NoPrefix | TextFormatFlags.EndEllipsis);
}
}
static class TogglePainter
{
const TextFormatFlags Flags = TextFormatFlags.SingleLine | TextFormatFlags.NoPrefix;
static int Box(Control c)
{
return Math.Max(13, c.Font.Height);
}
public static Size Measure(ButtonBase c)
{
Size t = TextRenderer.MeasureText(c.Text, c.Font, Size.Empty, Flags);
int box = Box(c);
return new Size(box + Ui.S(8) + t.Width + Ui.S(2), Math.Max(box, t.Height) + Ui.S(4));
}
public static void Paint(ButtonBase c, Graphics g, bool on, bool radio, bool hover, bool focus)
{
Theme th = Theme.Current;
g.Clear(Ui.ParentBack(c));
int box = Box(c);
var r = new RectangleF(0.5f, (c.Height - box) / 2f + 0.5f, box - 1f, box - 1f);
Color accent = !c.Enabled ? th.AccentDisabled : hover ? th.AccentHover : th.Accent;
Color border = !c.Enabled ? th.TextDisabled : hover ? th.Text : th.ToggleBorder;
g.SmoothingMode = SmoothingMode.AntiAlias;
if (radio)
{
if (on)
{
using (var b = new SolidBrush(accent))
g.FillEllipse(b, r);
float d = box * 0.4f;
using (var b = new SolidBrush(th.AccentText))
g.FillEllipse(b, r.X + (r.Width - d) / 2f, r.Y + (r.Height - d) / 2f, d, d);
}
else
{
using (var b = new SolidBrush(th.ToggleFill))
g.FillEllipse(b, r);
using (var pen = new Pen(border))
g.DrawEllipse(pen, r);
}
}
else
{
using (GraphicsPath p = Ui.Round(r, box * 0.2f))
{
if (on)
{
using (var b = new SolidBrush(accent))
g.FillPath(b, p);
using (var pen = new Pen(th.AccentText, Math.Max(1.5f, box * 0.12f)))
{
pen.StartCap = pen.EndCap = LineCap.Round;
pen.LineJoin = LineJoin.Round;
g.DrawLines(pen, new[]
{
new PointF(r.X + box * 0.25f, r.Y + box * 0.52f),
new PointF(r.X + box * 0.43f, r.Y + box * 0.70f),
new PointF(r.X + box * 0.76f, r.Y + box * 0.32f),
});
}
}
else
{
using (var b = new SolidBrush(th.ToggleFill))
g.FillPath(b, p);
using (var pen = new Pen(border))
g.DrawPath(pen, p);
}
}
}
g.SmoothingMode = SmoothingMode.None;
var tr = new Rectangle(box + Ui.S(8), 0, Math.Max(0, c.Width - box - Ui.S(8)), c.Height);
TextRenderer.DrawText(g, c.Text, c.Font, tr, c.Enabled ? th.Text : th.TextDisabled,
Flags | TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.EndEllipsis);
if (focus)
{
Size t = TextRenderer.MeasureText(c.Text, c.Font, Size.Empty, Flags);
ControlPaint.DrawFocusRectangle(g, new Rectangle(tr.X - 1, (c.Height - t.Height) / 2, t.Width + 2, t.Height),
th.Text, Ui.ParentBack(c));
}
}
}
sealed class ThemedCheck : CheckBox
{
bool hover;
public ThemedCheck()
{
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true);
}
public override Size GetPreferredSize(Size proposed)
{
return TogglePainter.Measure(this);
}
protected override void OnPaintBackground(PaintEventArgs e)
{
}
protected override void OnPaint(PaintEventArgs e)
{
TogglePainter.Paint(this, e.Graphics, Checked, false, hover, Focused && ShowFocusCues);
}
protected override void OnMouseEnter(EventArgs e)
{
hover = true;
Invalidate();
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(EventArgs e)
{
hover = false;
Invalidate();
base.OnMouseLeave(e);
}
}
sealed class ThemedRadio : RadioButton
{
bool hover;
public ThemedRadio()
{
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true);
}
public override Size GetPreferredSize(Size proposed)
{
return TogglePainter.Measure(this);
}
protected override void OnPaintBackground(PaintEventArgs e)
{
}
protected override void OnPaint(PaintEventArgs e)
{
TogglePainter.Paint(this, e.Graphics, Checked, true, hover, Focused && ShowFocusCues);
}
protected override void OnMouseEnter(EventArgs e)
{
hover = true;
Invalidate();
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(EventArgs e)
{
hover = false;
Invalidate();
base.OnMouseLeave(e);
}
}
enum BarState { Running, Ok, Error, Paused }
// Thin rounded progress bar; Marquee = "busy, amount unknown"
sealed class FlatProgress : Control
{
int value;
bool marquee;
BarState state;
float phase;
readonly Timer timer = new Timer { Interval = 15 };
public FlatProgress()
{
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true);
SetStyle(ControlStyles.Selectable, false);
timer.Tick += (s, e) =>
{
phase += 0.009f;
if (phase > 1f)
phase -= 1f;
Invalidate();
};
}
public int Value
{
get { return value; }
set { this.value = Math.Max(0, Math.Min(100, value)); Invalidate(); }
}
public bool Marquee
{
get { return marquee; }
set { marquee = value; phase = 0; timer.Enabled = value; Invalidate(); }
}
public BarState State
{
get { return state; }
set { state = value; Invalidate(); }
}
protected override void OnPaintBackground(PaintEventArgs e)
{
}
protected override void OnPaint(PaintEventArgs e)
{
Theme th = Theme.Current;
Graphics g = e.Graphics;
g.Clear(Ui.ParentBack(this));
g.SmoothingMode = SmoothingMode.AntiAlias;
float h = Math.Min(Height, Ui.S(6));
var track = new RectangleF(0, (Height - h) / 2f, Width, h);
using (GraphicsPath p = Ui.Round(track, h / 2f))
using (var b = new SolidBrush(th.Track))
g.FillPath(b, p);
RectangleF bar;
if (marquee)
{
float seg = Width * 0.28f;
// ease in-out sweep across the track
float t = (float)(0.5 - Math.Cos(phase * Math.PI) / 2);
bar = RectangleF.Intersect(new RectangleF(-seg + t * (Width + seg), track.Y, seg, h), track);
}
else
{
bar = new RectangleF(0, track.Y, Width * value / 100f, h);
}
if (bar.Width < 0.5f)
return;
Color fill = state == BarState.Ok ? th.Ok : state == BarState.Error ? th.Err :
state == BarState.Paused ? th.Warn : th.Accent;
using (GraphicsPath p = Ui.Round(bar, h / 2f))
using (var b = new SolidBrush(fill))
g.FillPath(b, p);
}
protected override void Dispose(bool disposing)
{
if (disposing)
timer.Dispose();
base.Dispose(disposing);
}
}
sealed class StatusDot : Control
{
Color dotColor = Color.Silver;
public StatusDot()
{
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
}
public Color DotColor
{
get { return dotColor; }
set { dotColor = value; Invalidate(); }
}
protected override void OnPaintBackground(PaintEventArgs e)
{
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.Clear(Ui.ParentBack(this));
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (var b = new SolidBrush(dotColor))
e.Graphics.FillEllipse(b, 0.5f, 0.5f, Width - 1.5f, Height - 1.5f);
}
}
// Context menus in the colors of the current theme
sealed class MenuRenderer : ToolStripProfessionalRenderer
{
public MenuRenderer() : base(new MenuColors())
{
RoundedEdges = false;
}
protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e)
{
e.TextColor = e.Item.Enabled ? Theme.Current.Text : Theme.Current.TextDisabled;
base.OnRenderItemText(e);
}
protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e)
{
Rectangle r = e.ImageRectangle;
float s = Math.Min(r.Width, r.Height);
float x = r.X + (r.Width - s) / 2f, y = r.Y + (r.Height - s) / 2f;
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (var pen = new Pen(Theme.Current.Text, Math.Max(1.5f, s * 0.11f)))
{
pen.StartCap = pen.EndCap = LineCap.Round;
pen.LineJoin = LineJoin.Round;
e.Graphics.DrawLines(pen, new[]
{
new PointF(x + s * 0.22f, y + s * 0.52f),
new PointF(x + s * 0.42f, y + s * 0.72f),
new PointF(x + s * 0.78f, y + s * 0.30f),
});
}
e.Graphics.SmoothingMode = SmoothingMode.None;
}
sealed class MenuColors : ProfessionalColorTable
{
public MenuColors()
{
UseSystemColors = false;
}
static Theme T
{
get { return Theme.Current; }
}
public override Color ToolStripDropDownBackground { get { return T.MenuBg; } }
public override Color ImageMarginGradientBegin { get { return T.MenuBg; } }
public override Color ImageMarginGradientMiddle { get { return T.MenuBg; } }
public override Color ImageMarginGradientEnd { get { return T.MenuBg; } }
public override Color MenuBorder { get { return T.MenuBorder; } }
public override Color MenuItemBorder { get { return T.MenuHover; } }
public override Color MenuItemSelected { get { return T.MenuHover; } }
public override Color MenuItemSelectedGradientBegin { get { return T.MenuHover; } }
public override Color MenuItemSelectedGradientEnd { get { return T.MenuHover; } }
public override Color SeparatorDark { get { return T.MenuBorder; } }
public override Color SeparatorLight { get { return T.MenuBg; } }
public override Color CheckBackground { get { return T.MenuBg; } }
public override Color CheckSelectedBackground { get { return T.MenuHover; } }
public override Color CheckPressedBackground { get { return T.MenuHover; } }
}
}
}