feat: add FilterPanel with layers, colors, linetypes, bend lines
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,257 @@
|
|||||||
|
using OpenNest.Bending;
|
||||||
|
using OpenNest.Geometry;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace OpenNest.Controls
|
||||||
|
{
|
||||||
|
public class FilterPanel : Panel
|
||||||
|
{
|
||||||
|
private readonly CollapsiblePanel layersPanel;
|
||||||
|
private readonly CollapsiblePanel colorsPanel;
|
||||||
|
private readonly CollapsiblePanel lineTypesPanel;
|
||||||
|
private readonly CollapsiblePanel bendLinesPanel;
|
||||||
|
|
||||||
|
private readonly CheckedListBox layersList;
|
||||||
|
private readonly CheckedListBox colorsList;
|
||||||
|
private readonly CheckedListBox lineTypesList;
|
||||||
|
private readonly ListBox bendLinesList;
|
||||||
|
|
||||||
|
private List<Entity> currentEntities;
|
||||||
|
private List<Bend> currentBends;
|
||||||
|
|
||||||
|
public event EventHandler FilterChanged;
|
||||||
|
public event EventHandler<int> BendLineSelected;
|
||||||
|
public event EventHandler<int> BendLineRemoved;
|
||||||
|
|
||||||
|
public FilterPanel()
|
||||||
|
{
|
||||||
|
AutoScroll = true;
|
||||||
|
BackColor = Color.White;
|
||||||
|
|
||||||
|
// Bend Lines
|
||||||
|
bendLinesPanel = new CollapsiblePanel
|
||||||
|
{
|
||||||
|
HeaderText = "Bend Lines (0)",
|
||||||
|
Dock = DockStyle.Top,
|
||||||
|
ExpandedHeight = 120,
|
||||||
|
IsExpanded = false
|
||||||
|
};
|
||||||
|
bendLinesList = new ListBox
|
||||||
|
{
|
||||||
|
Dock = DockStyle.Fill,
|
||||||
|
BorderStyle = BorderStyle.None,
|
||||||
|
Font = new Font("Segoe UI", 9f)
|
||||||
|
};
|
||||||
|
bendLinesList.SelectedIndexChanged += (s, e) =>
|
||||||
|
BendLineSelected?.Invoke(this, bendLinesList.SelectedIndex);
|
||||||
|
|
||||||
|
var bendDeleteLink = new LinkLabel
|
||||||
|
{
|
||||||
|
Text = "Remove Selected",
|
||||||
|
Dock = DockStyle.Bottom,
|
||||||
|
Height = 20,
|
||||||
|
Font = new Font("Segoe UI", 8f)
|
||||||
|
};
|
||||||
|
bendDeleteLink.LinkClicked += (s, e) =>
|
||||||
|
{
|
||||||
|
if (bendLinesList.SelectedIndex >= 0)
|
||||||
|
BendLineRemoved?.Invoke(this, bendLinesList.SelectedIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
bendLinesPanel.ContentPanel.Controls.Add(bendLinesList);
|
||||||
|
bendLinesPanel.ContentPanel.Controls.Add(bendDeleteLink);
|
||||||
|
|
||||||
|
// Line Types
|
||||||
|
lineTypesPanel = new CollapsiblePanel
|
||||||
|
{
|
||||||
|
HeaderText = "Line Types (0)",
|
||||||
|
Dock = DockStyle.Top,
|
||||||
|
ExpandedHeight = 100,
|
||||||
|
IsExpanded = false
|
||||||
|
};
|
||||||
|
lineTypesList = CreateCheckedList();
|
||||||
|
lineTypesPanel.ContentPanel.Controls.Add(lineTypesList);
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
colorsPanel = new CollapsiblePanel
|
||||||
|
{
|
||||||
|
HeaderText = "Colors (0)",
|
||||||
|
Dock = DockStyle.Top,
|
||||||
|
ExpandedHeight = 100,
|
||||||
|
IsExpanded = false
|
||||||
|
};
|
||||||
|
colorsList = CreateCheckedList();
|
||||||
|
colorsList.DrawMode = DrawMode.OwnerDrawFixed;
|
||||||
|
colorsList.ItemHeight = 20;
|
||||||
|
colorsList.DrawItem += ColorsList_DrawItem;
|
||||||
|
colorsPanel.ContentPanel.Controls.Add(colorsList);
|
||||||
|
|
||||||
|
// Layers (always expanded)
|
||||||
|
layersPanel = new CollapsiblePanel
|
||||||
|
{
|
||||||
|
HeaderText = "Layers",
|
||||||
|
Dock = DockStyle.Top,
|
||||||
|
ExpandedHeight = 160,
|
||||||
|
IsExpanded = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var checkAllPanel = new Panel { Dock = DockStyle.Top, Height = 22 };
|
||||||
|
var checkAll = new LinkLabel { Text = "All", AutoSize = true, Location = new Point(4, 2), Font = new Font("Segoe UI", 8f) };
|
||||||
|
var uncheckAll = new LinkLabel { Text = "None", AutoSize = true, Location = new Point(30, 2), Font = new Font("Segoe UI", 8f) };
|
||||||
|
checkAll.LinkClicked += (s, e) => SetAllChecked(layersList, true);
|
||||||
|
uncheckAll.LinkClicked += (s, e) => SetAllChecked(layersList, false);
|
||||||
|
checkAllPanel.Controls.AddRange(new Control[] { checkAll, uncheckAll });
|
||||||
|
|
||||||
|
layersList = CreateCheckedList();
|
||||||
|
layersPanel.ContentPanel.Controls.Add(layersList);
|
||||||
|
layersPanel.ContentPanel.Controls.Add(checkAllPanel);
|
||||||
|
|
||||||
|
// Add panels in reverse order (Dock.Top stacks top-down)
|
||||||
|
Controls.Add(bendLinesPanel);
|
||||||
|
Controls.Add(lineTypesPanel);
|
||||||
|
Controls.Add(colorsPanel);
|
||||||
|
Controls.Add(layersPanel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CheckedListBox CreateCheckedList()
|
||||||
|
{
|
||||||
|
var list = new CheckedListBox
|
||||||
|
{
|
||||||
|
Dock = DockStyle.Fill,
|
||||||
|
BorderStyle = BorderStyle.None,
|
||||||
|
CheckOnClick = true,
|
||||||
|
Font = new Font("Segoe UI", 9f)
|
||||||
|
};
|
||||||
|
list.ItemCheck += (s, e) =>
|
||||||
|
BeginInvoke((Action)(() => FilterChanged?.Invoke(this, EventArgs.Empty)));
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadItem(List<Entity> entities, List<Bend> bends)
|
||||||
|
{
|
||||||
|
currentEntities = entities;
|
||||||
|
currentBends = bends;
|
||||||
|
|
||||||
|
// Layers
|
||||||
|
layersList.Items.Clear();
|
||||||
|
var layers = entities
|
||||||
|
.Where(e => e.Layer != null)
|
||||||
|
.Select(e => e.Layer.Name)
|
||||||
|
.Distinct();
|
||||||
|
foreach (var layer in layers)
|
||||||
|
layersList.Items.Add(layer, true); // checked = visible
|
||||||
|
|
||||||
|
layersPanel.HeaderText = $"Layers ({layersList.Items.Count})";
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
colorsList.Items.Clear();
|
||||||
|
var colors = entities
|
||||||
|
.Select(e => e.Color.ToArgb())
|
||||||
|
.Distinct()
|
||||||
|
.Select(argb => new ColorItem(Color.FromArgb(argb)));
|
||||||
|
foreach (var color in colors)
|
||||||
|
colorsList.Items.Add(color, true); // checked = visible
|
||||||
|
|
||||||
|
colorsPanel.HeaderText = $"Colors ({colorsList.Items.Count})";
|
||||||
|
|
||||||
|
// Line Types
|
||||||
|
lineTypesList.Items.Clear();
|
||||||
|
var lineTypes = entities
|
||||||
|
.Select(e => e.LineTypeName ?? "Continuous")
|
||||||
|
.Distinct();
|
||||||
|
foreach (var lt in lineTypes)
|
||||||
|
lineTypesList.Items.Add(lt, true); // checked = visible
|
||||||
|
|
||||||
|
lineTypesPanel.HeaderText = $"Line Types ({lineTypesList.Items.Count})";
|
||||||
|
|
||||||
|
// Bend Lines
|
||||||
|
bendLinesList.Items.Clear();
|
||||||
|
if (bends != null)
|
||||||
|
{
|
||||||
|
foreach (var bend in bends)
|
||||||
|
bendLinesList.Items.Add(bend.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
var bendCount = bends?.Count ?? 0;
|
||||||
|
bendLinesPanel.HeaderText = $"Bend Lines ({bendCount})";
|
||||||
|
bendLinesPanel.IsExpanded = bendCount > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyFilters(List<Entity> entities)
|
||||||
|
{
|
||||||
|
var hiddenLayers = new HashSet<string>();
|
||||||
|
for (var i = 0; i < layersList.Items.Count; i++)
|
||||||
|
{
|
||||||
|
if (!layersList.GetItemChecked(i))
|
||||||
|
hiddenLayers.Add(layersList.Items[i].ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
var hiddenColors = new HashSet<int>();
|
||||||
|
for (var i = 0; i < colorsList.Items.Count; i++)
|
||||||
|
{
|
||||||
|
if (!colorsList.GetItemChecked(i))
|
||||||
|
hiddenColors.Add(((ColorItem)colorsList.Items[i]).Argb);
|
||||||
|
}
|
||||||
|
|
||||||
|
var hiddenLineTypes = new HashSet<string>();
|
||||||
|
for (var i = 0; i < lineTypesList.Items.Count; i++)
|
||||||
|
{
|
||||||
|
if (!lineTypesList.GetItemChecked(i))
|
||||||
|
hiddenLineTypes.Add(lineTypesList.Items[i].ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var entity in entities)
|
||||||
|
{
|
||||||
|
var layerVisible = entity.Layer?.Name == null || !hiddenLayers.Contains(entity.Layer.Name);
|
||||||
|
var colorVisible = !hiddenColors.Contains(entity.Color.ToArgb());
|
||||||
|
var ltVisible = !hiddenLineTypes.Contains(entity.LineTypeName ?? "Continuous");
|
||||||
|
|
||||||
|
entity.IsVisible = layerVisible && colorVisible && ltVisible;
|
||||||
|
if (entity.Layer != null)
|
||||||
|
entity.Layer.IsVisible = !hiddenLayers.Contains(entity.Layer.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetAllChecked(CheckedListBox list, bool isChecked)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < list.Items.Count; i++)
|
||||||
|
list.SetItemChecked(i, isChecked);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ColorsList_DrawItem(object sender, DrawItemEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Index < 0) return;
|
||||||
|
|
||||||
|
e.DrawBackground();
|
||||||
|
|
||||||
|
var colorItem = (ColorItem)colorsList.Items[e.Index];
|
||||||
|
var swatchRect = new Rectangle(e.Bounds.Left + 20, e.Bounds.Top + 2, 16, e.Bounds.Height - 4);
|
||||||
|
|
||||||
|
using (var brush = new SolidBrush(colorItem.Color))
|
||||||
|
e.Graphics.FillRectangle(brush, swatchRect);
|
||||||
|
e.Graphics.DrawRectangle(Pens.Gray, swatchRect);
|
||||||
|
|
||||||
|
e.DrawFocusRectangle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ColorItem
|
||||||
|
{
|
||||||
|
public int Argb { get; }
|
||||||
|
public Color Color { get; }
|
||||||
|
|
||||||
|
public ColorItem(Color color)
|
||||||
|
{
|
||||||
|
Color = color;
|
||||||
|
Argb = color.ToArgb();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => $"#{Color.R:X2}{Color.G:X2}{Color.B:X2}";
|
||||||
|
public override bool Equals(object obj) => obj is ColorItem other && Argb == other.Argb;
|
||||||
|
public override int GetHashCode() => Argb;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user