加几个控件

This commit is contained in:
2026-03-26 20:13:29 +08:00
parent 2c2f93c982
commit 2c2ee0f1de
8 changed files with 427 additions and 4 deletions

View File

@@ -1 +1 @@
2026-03-26 19:55:43 2026-03-26 20:06:05

View File

@@ -1 +1 @@
b607cee 2c2f93c

View File

@@ -18,6 +18,7 @@ namespace CMLeonOS.Gui.Apps
private readonly List<Window> demoWindows = new List<Window>(); private readonly List<Window> demoWindows = new List<Window>();
private FileBrowser fileBrowser; private FileBrowser fileBrowser;
private ToolTip toolTip;
private string headerTitle = "UILib Gallery"; private string headerTitle = "UILib Gallery";
private string headerDescription = "Browse and test the current UILib controls."; private string headerDescription = "Browse and test the current UILib controls.";
@@ -186,6 +187,21 @@ namespace CMLeonOS.Gui.Apps
AddDemo(hint); AddDemo(hint);
} }
private void ShowProgressDemo()
{
ClearPreview();
SetHeader("ProgressRing", "Animated busy indicator for indeterminate loading states.");
ProgressRing ring = new ProgressRing(previewHost, 24, 24, 56, 56);
ring.Active = true;
AddDemo(ring);
TextBlock hint = new TextBlock(previewHost, 100, 38, 320, 24);
hint.Text = "ProgressRing keeps animating while Active=true.";
hint.Foreground = UITheme.TextSecondary;
AddDemo(hint);
}
private void ShowTableDemo() private void ShowTableDemo()
{ {
ClearPreview(); ClearPreview();
@@ -239,6 +255,35 @@ namespace CMLeonOS.Gui.Apps
statusBar.Text = "Ready"; statusBar.Text = "Ready";
statusBar.DetailText = "UILib Demo"; statusBar.DetailText = "UILib Demo";
AddDemo(statusBar); AddDemo(statusBar);
Separator separator = new Separator(previewHost, 20, 172, 360, 8);
separator.Background = Color.FromArgb(250, 252, 255);
separator.Foreground = UITheme.SurfaceBorder;
AddDemo(separator);
}
private void ShowTreeDemo()
{
ClearPreview();
SetHeader("TreeView", "Hierarchical tree control for folders, settings and project structures.");
TreeView tree = new TreeView(previewHost, 20, 24, 300, 180);
TreeNode rootA = new TreeNode("System");
rootA.Expanded = true;
rootA.Children.Add(new TreeNode("Apps"));
rootA.Children.Add(new TreeNode("Config"));
TreeNode rootB = new TreeNode("User");
rootB.Children.Add(new TreeNode("Desktop"));
rootB.Children.Add(new TreeNode("Documents"));
rootB.Children.Add(new TreeNode("Pictures"));
tree.Nodes.Add(rootA);
tree.Nodes.Add(rootB);
tree.NodeSelected = (node) => SetHeader("TreeView", "Selected node: " + node.Text);
tree.Render();
AddDemo(tree);
} }
private void ShowDialogsDemo() private void ShowDialogsDemo()
@@ -286,6 +331,20 @@ namespace CMLeonOS.Gui.Apps
notification.Show(); notification.Show();
}; };
AddDemo(notificationButton); AddDemo(notificationButton);
Button toolTipButton = new Button(previewHost, 172, 68, 140, 28);
toolTipButton.Text = "ToolTip";
toolTipButton.OnClick = (_, _) =>
{
if (toolTip == null)
{
toolTip = new ToolTip(this, wm);
}
toolTip.Show(toolTipButton, "Manual ToolTip demo");
SetHeader("Dialogs", "ToolTip shown near the button.");
};
toolTipButton.OnUnfocused = () => toolTip?.Hide();
AddDemo(toolTipButton);
} }
private void CategorySelected(int index) private void CategorySelected(int index)
@@ -308,12 +367,18 @@ namespace CMLeonOS.Gui.Apps
ShowNumericDemo(); ShowNumericDemo();
break; break;
case 5: case 5:
ShowTableDemo(); ShowProgressDemo();
break; break;
case 6: case 6:
ShowBarsDemo(); ShowTableDemo();
break; break;
case 7: case 7:
ShowTreeDemo();
break;
case 8:
ShowBarsDemo();
break;
case 9:
ShowDialogsDemo(); ShowDialogsDemo();
break; break;
} }
@@ -363,7 +428,9 @@ namespace CMLeonOS.Gui.Apps
categoryTable.Cells.Add(new TableCell("Dropdown")); categoryTable.Cells.Add(new TableCell("Dropdown"));
categoryTable.Cells.Add(new TableCell("Tabs")); categoryTable.Cells.Add(new TableCell("Tabs"));
categoryTable.Cells.Add(new TableCell("Numeric")); categoryTable.Cells.Add(new TableCell("Numeric"));
categoryTable.Cells.Add(new TableCell("Progress"));
categoryTable.Cells.Add(new TableCell("Table")); categoryTable.Cells.Add(new TableCell("Table"));
categoryTable.Cells.Add(new TableCell("TreeView"));
categoryTable.Cells.Add(new TableCell("Bars && Blocks")); categoryTable.Cells.Add(new TableCell("Bars && Blocks"));
categoryTable.Cells.Add(new TableCell("Dialogs")); categoryTable.Cells.Add(new TableCell("Dialogs"));
wm.AddWindow(categoryTable); wm.AddWindow(categoryTable);

91
Gui/UILib/ProgressRing.cs Normal file
View File

@@ -0,0 +1,91 @@
using System;
using System.Drawing;
namespace CMLeonOS.Gui.UILib
{
internal class ProgressRing : Control
{
public ProgressRing(Window parent, int x, int y, int width, int height) : base(parent, x, y, width, height)
{
}
private Color _background = UITheme.Surface;
internal Color Background
{
get { return _background; }
set { _background = value; Render(); }
}
private Color _ringColor = UITheme.Accent;
internal Color RingColor
{
get { return _ringColor; }
set { _ringColor = value; Render(); }
}
private Color _trackColor = UITheme.SurfaceBorder;
internal Color TrackColor
{
get { return _trackColor; }
set { _trackColor = value; Render(); }
}
private bool _active = true;
internal bool Active
{
get { return _active; }
set { _active = value; Render(); }
}
internal int Thickness { get; set; } = 3;
private int frame = 0;
internal override void Render()
{
Clear(Background);
int radius = Math.Max(6, (Math.Min(Width, Height) / 2) - 4);
int centerX = Width / 2;
int centerY = Height / 2;
for (int angle = 0; angle < 360; angle += 12)
{
DrawArcPoint(centerX, centerY, radius, angle, TrackColor);
}
if (Active)
{
for (int i = 0; i < 8; i++)
{
int angle = (frame + (i * 18)) % 360;
Color color = Color.FromArgb(
Math.Max(60, RingColor.R - (i * 12)),
Math.Max(60, RingColor.G - (i * 12)),
Math.Max(60, RingColor.B - (i * 12)));
DrawArcPoint(centerX, centerY, radius, angle, color);
}
frame = (frame + 10) % 360;
WM.UpdateQueue.Enqueue(this);
}
WM.Update(this);
}
private void DrawArcPoint(int centerX, int centerY, int radius, int angleDegrees, Color color)
{
double radians = angleDegrees * (Math.PI / 180d);
int pointX = centerX + (int)(Math.Cos(radians) * radius);
int pointY = centerY + (int)(Math.Sin(radians) * radius);
for (int y = -Thickness / 2; y <= Thickness / 2; y++)
{
for (int x = -Thickness / 2; x <= Thickness / 2; x++)
{
DrawPoint(pointX + x, pointY + y, color);
}
}
}
}
}

51
Gui/UILib/Separator.cs Normal file
View File

@@ -0,0 +1,51 @@
using System.Drawing;
namespace CMLeonOS.Gui.UILib
{
internal class Separator : Control
{
internal enum SeparatorOrientation
{
Horizontal,
Vertical
}
public Separator(Window parent, int x, int y, int width, int height) : base(parent, x, y, width, height)
{
}
private Color _background = UITheme.Surface;
internal Color Background
{
get { return _background; }
set { _background = value; Render(); }
}
private Color _foreground = UITheme.SurfaceBorder;
internal Color Foreground
{
get { return _foreground; }
set { _foreground = value; Render(); }
}
internal SeparatorOrientation Orientation { get; set; } = SeparatorOrientation.Horizontal;
internal override void Render()
{
Clear(Background);
if (Orientation == SeparatorOrientation.Horizontal)
{
int y = Height / 2;
DrawHorizontalLine(Width, 0, y, Foreground);
}
else
{
int x = Width / 2;
DrawVerticalLine(Height, x, 0, Foreground);
}
WM.Update(this);
}
}
}

56
Gui/UILib/ToolTip.cs Normal file
View File

@@ -0,0 +1,56 @@
using System;
using System.Drawing;
namespace CMLeonOS.Gui.UILib
{
internal class ToolTip
{
internal ToolTip(Process process, WindowManager wm)
{
this.process = process;
this.wm = wm;
}
private readonly Process process;
private readonly WindowManager wm;
private Window window;
internal void Show(Window anchor, string text, int offsetX = 0, int offsetY = 0)
{
Hide();
string message = text ?? string.Empty;
int width = Math.Max(80, (message.Length * 8) + 12);
int height = 24;
int x = anchor.ScreenX + offsetX;
int y = anchor.ScreenY + anchor.Height + 6 + offsetY;
if (x + width > wm.ScreenWidth)
{
x = (int)wm.ScreenWidth - width - 6;
}
if (y + height > wm.ScreenHeight)
{
y = anchor.ScreenY - height - 6;
}
window = new Window(process, x, y, width, height);
window.Clear(Color.FromArgb(33, 39, 49));
window.DrawFilledRectangle(0, 0, width, height, Color.FromArgb(33, 39, 49));
window.DrawRectangle(0, 0, width, height, UITheme.SurfaceBorder);
window.DrawString(message, Color.White, 6, 4);
wm.AddWindow(window);
wm.Update(window);
}
internal void Hide()
{
if (window != null)
{
wm.RemoveWindow(window);
window = null;
}
}
}
}

18
Gui/UILib/TreeNode.cs Normal file
View File

@@ -0,0 +1,18 @@
using System.Collections.Generic;
namespace CMLeonOS.Gui.UILib
{
internal class TreeNode
{
internal TreeNode(string text, object tag = null)
{
Text = text;
Tag = tag;
}
internal string Text { get; set; }
internal object Tag { get; set; }
internal bool Expanded { get; set; } = false;
internal List<TreeNode> Children { get; } = new List<TreeNode>();
}
}

140
Gui/UILib/TreeView.cs Normal file
View File

@@ -0,0 +1,140 @@
using System;
using System.Collections.Generic;
using System.Drawing;
namespace CMLeonOS.Gui.UILib
{
internal class TreeView : Control
{
public TreeView(Window parent, int x, int y, int width, int height) : base(parent, x, y, width, height)
{
OnClick = TreeViewClicked;
}
internal Action<TreeNode> NodeSelected;
internal List<TreeNode> Nodes { get; } = new List<TreeNode>();
private readonly List<TreeNode> visibleNodes = new List<TreeNode>();
private readonly List<int> visibleLevels = new List<int>();
private Color _background = UITheme.Surface;
internal Color Background
{
get { return _background; }
set { _background = value; Render(); }
}
private Color _foreground = UITheme.TextPrimary;
internal Color Foreground
{
get { return _foreground; }
set { _foreground = value; Render(); }
}
private Color _border = UITheme.SurfaceBorder;
internal Color Border
{
get { return _border; }
set { _border = value; Render(); }
}
private Color _selectedBackground = UITheme.AccentLight;
internal Color SelectedBackground
{
get { return _selectedBackground; }
set { _selectedBackground = value; Render(); }
}
internal int RowHeight { get; set; } = 22;
internal TreeNode SelectedNode { get; private set; }
private void BuildVisibleNodes()
{
visibleNodes.Clear();
visibleLevels.Clear();
for (int i = 0; i < Nodes.Count; i++)
{
AddVisibleNode(Nodes[i], 0);
}
}
private void AddVisibleNode(TreeNode node, int level)
{
visibleNodes.Add(node);
visibleLevels.Add(level);
if (node.Expanded)
{
for (int i = 0; i < node.Children.Count; i++)
{
AddVisibleNode(node.Children[i], level + 1);
}
}
}
private void TreeViewClicked(int x, int y)
{
BuildVisibleNodes();
int index = y / RowHeight;
if (index < 0 || index >= visibleNodes.Count)
{
return;
}
TreeNode node = visibleNodes[index];
int level = visibleLevels[index];
int indentX = 8 + (level * 16);
if (node.Children.Count > 0 && x >= indentX && x <= indentX + 10)
{
node.Expanded = !node.Expanded;
Render();
return;
}
SelectedNode = node;
NodeSelected?.Invoke(node);
Render();
}
internal override void Render()
{
Clear(Background);
DrawRectangle(0, 0, Width, Height, Border);
BuildVisibleNodes();
int maxRows = Math.Min(visibleNodes.Count, Math.Max(0, Height / RowHeight));
for (int i = 0; i < maxRows; i++)
{
TreeNode node = visibleNodes[i];
int level = visibleLevels[i];
int rowY = i * RowHeight;
if (node == SelectedNode)
{
DrawFilledRectangle(1, rowY + 1, Width - 2, RowHeight - 2, SelectedBackground);
}
int indentX = 8 + (level * 16);
if (node.Children.Count > 0)
{
DrawRectangle(indentX, rowY + 6, 10, 10, Border);
DrawHorizontalLine(6, indentX + 2, rowY + 11, Foreground);
if (!node.Expanded)
{
DrawVerticalLine(6, indentX + 5, rowY + 8, Foreground);
}
}
int textX = indentX + (node.Children.Count > 0 ? 16 : 8);
DrawString(node.Text, Foreground, textX, rowY + 3);
}
WM.Update(this);
}
}
}