You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kev/Drawer/UCDraw/SigmaDrawerStyle/FrmGradientRulerEditor.cs

388 lines
16 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using DevExpress.Utils.Drawing;
using DevExpress.XtraEditors;
using GeoSigmaDrawLib;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Windows.Forms;
namespace GeoSigma.SigmaDrawerStyle
{
public class FrmGradientRulerEditor : XtraUserControl
{
public class MarkItem
{
public ColorScaleEntry RawData;
public double NormalizedPosition { get; set; }
internal float CurrentY { get; set; }
internal RectangleF Bounds { get; set; }
internal RectangleF TextBounds { get; set; }
internal bool IsHovered { get; set; }
internal bool IsSelected { get; set; }
}
private List<MarkItem> _items = new List<MarkItem>();
private MarkItem _draggingItem = null;
private MarkItem _editingItem = null;
private MarkItem _selectedItem = null;
private TextBox _editTextBox;
private double _globalMin, _globalMax;
private bool _isReversed; // 标记是否为反转状态
private ColorBlend _fixedGradientBlend;
private const int GradientWidth = 40;
private const int PaddingTopBottom = 30;
private Rectangle _gradRect;
public FrmGradientRulerEditor()
{
this.DoubleBuffered = true;
this.Font = new Font("Tahoma", 9F);
_editTextBox = new TextBox { Visible = false, Width = 60 };
_editTextBox.Leave += (s, e) => EndEdit(true);
_editTextBox.KeyDown += (s, e) => {
if (e.KeyCode == Keys.Enter) EndEdit(true);
if (e.KeyCode == Keys.Escape) EndEdit(false);
};
this.Controls.Add(_editTextBox);
}
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == Keys.Delete)
{
DeleteSelected();
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
// 接收是否反转的参数
public void SetData(IEnumerable<ColorScaleEntry> bgEntries, IEnumerable<ColorScaleEntry> markEntries, double min, double max, bool isReversed)
{
_globalMin = min;
_globalMax = max;
_isReversed = isReversed;
double range = max - min;
UpdateGradientBlend(bgEntries, min, max);
_items.Clear();
foreach (var entry in markEntries)
{
double pos = (range == 0) ? 0.5 : (entry.z - min) / range;
_items.Add(new MarkItem { RawData = entry, NormalizedPosition = pos });
}
Invalidate();
}
private void UpdateGradientBlend(IEnumerable<ColorScaleEntry> entries, double min, double max)
{
var baseSorted = entries.OrderBy(e => e.z).ToList();
if (baseSorted.Count > 1)
{
float[] posArray = new float[baseSorted.Count];
Color[] colorArray = new Color[baseSorted.Count];
double range = max - min;
for (int i = 0; i < baseSorted.Count; i++)
{
float pos = (range == 0) ? 0 : (float)((baseSorted[i].z - min) / range);
//强制限制在 0.0 ~ 1.0 之间,防越界崩溃
posArray[i] = Math.Max(0.0f, Math.Min(1.0f, pos));
colorArray[i] = Color.FromArgb(baseSorted[i].a, baseSorted[i].r, baseSorted[i].g, baseSorted[i].b);
}
if (!_isReversed)
{
for (int i = 0; i < posArray.Length; i++) posArray[i] = 1.0f - posArray[i];
}
Array.Sort(posArray, colorArray);
// GDI+ 终极保险:强制首尾两端绝对等于 0.0 和 1.0
posArray[0] = 0.0f;
posArray[posArray.Length - 1] = 1.0f;
// 防重叠崩溃GDI+ 要求位置必须严格递增,如果有两个节点在同一位置会直接崩溃
for (int i = 1; i < posArray.Length; i++)
{
if (posArray[i] <= posArray[i - 1])
{
posArray[i] = posArray[i - 1] + 0.00001f; // 微微偏移
if (posArray[i] > 1.0f) posArray[i] = 1.0f;
}
}
// 再次确保最后一位是 1.0f (防偏移挤压)
posArray[posArray.Length - 1] = 1.0f;
_fixedGradientBlend = new ColorBlend { Colors = colorArray, Positions = posArray };
}
}
public void AddPointByValue(double val)
{
val = Math.Max(_globalMin, Math.Min(_globalMax, val));
// 【新增防重叠】:检查是否已经存在非常接近该 Z 值的节点
if (_items.Any(item => Math.Abs(item.RawData.z - val) < 1e-5))
{
// 如果是用户点击按钮手动输入,给个弹窗提示;双击误触则静默忽略
XtraMessageBox.Show("已存在相同数值的标注点!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
double range = _globalMax - _globalMin;
double pos = (range == 0) ? 0.5 : (val - _globalMin) / range;
// 获取采样色
float blendPos = _isReversed ? (float)pos : 1.0f - (float)pos;
Color sampled = GetInterpolatedColor(_fixedGradientBlend, blendPos);
var entry = new ColorScaleEntry { z = val, r = sampled.R, g = sampled.G, b = sampled.B, a = sampled.A, isContinue = 1 };
var newItem = new MarkItem { RawData = entry, NormalizedPosition = pos };
_items.Add(newItem);
SetSelectedItem(newItem);
Invalidate();
}
public void DeleteSelected()
{
if (_selectedItem != null && _items.Count > 1)
{
_items.Remove(_selectedItem);
_selectedItem = null;
Invalidate();
}
}
private void SetSelectedItem(MarkItem item)
{
_items.ForEach(i => i.IsSelected = false);
if (item != null) item.IsSelected = true;
_selectedItem = item;
Invalidate();
}
protected override void OnMouseDown(MouseEventArgs e)
{
if (_editTextBox.Visible) { EndEdit(true); return; }
var hit = _items.FirstOrDefault(p => p.Bounds.Contains(e.Location));
if (hit != null)
{
SetSelectedItem(hit);
_draggingItem = hit;
this.Focus();
Capture = true;
}
else
{
SetSelectedItem(null);
}
}
protected override void OnMouseDoubleClick(MouseEventArgs e)
{
var hitText = _items.FirstOrDefault(p => p.TextBounds.Contains(e.Location));
if (hitText != null) { StartEdit(hitText); return; }
if (_gradRect.Contains(e.Location))
{
float h = Height - (PaddingTopBottom * 2);
double ratio = (e.Y - PaddingTopBottom) / h;
// 正向时,从上往下数值递减;反向时,从上往下数值递增
if (!_isReversed) ratio = 1.0 - ratio;
double val = _globalMin + (_globalMax - _globalMin) * ratio;
AddPointByValue(val);
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (_editTextBox.Visible) return;
foreach (var item in _items) item.IsHovered = item.Bounds.Contains(e.Location);
if (_draggingItem != null)
{
float h = Height - (PaddingTopBottom * 2);
double newPos = (e.Y - PaddingTopBottom) / h;
if (!_isReversed) newPos = 1.0 - newPos; // 正反转映射
newPos = Math.Max(0.0, Math.Min(1.0, newPos));
_draggingItem.NormalizedPosition = newPos;
var data = _draggingItem.RawData;
data.z = _globalMin + (_globalMax - _globalMin) * newPos;
// 实时采样颜色
float blendPos = _isReversed ? (float)newPos : 1.0f - (float)newPos;
Color sampled = GetInterpolatedColor(_fixedGradientBlend, blendPos);
data.r = sampled.R;
data.g = sampled.G;
data.b = sampled.B;
_draggingItem.RawData = data;
}
Invalidate();
}
protected override void OnMouseUp(MouseEventArgs e) { _draggingItem = null; Capture = false; }
private void StartEdit(MarkItem item)
{
_editingItem = item;
_editTextBox.Text = item.RawData.z.ToString("F3");
_editTextBox.Location = new Point((int)item.TextBounds.X, (int)item.TextBounds.Y);
_editTextBox.Visible = true;
_editTextBox.Focus(); _editTextBox.SelectAll();
}
private void EndEdit(bool commit)
{
if (_editingItem != null && commit && double.TryParse(_editTextBox.Text, out double newVal))
{
newVal = Math.Max(_globalMin, Math.Min(_globalMax, newVal));
if (_items.Any(item => item != _editingItem && Math.Abs(item.RawData.z - newVal) < 1e-5))
{
XtraMessageBox.Show("已存在相同数值的标注点,修改被取消!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
else
{
var data = _editingItem.RawData;
data.z = newVal;
double range = _globalMax - _globalMin;
double pos = (range == 0) ? 0.5 : (newVal - _globalMin) / range;
_editingItem.NormalizedPosition = pos;
// 实时采样颜色
float blendPos = _isReversed ? (float)pos : 1.0f - (float)pos;
Color sampled = GetInterpolatedColor(_fixedGradientBlend, blendPos);
data.r = sampled.R;
data.g = sampled.G;
data.b = sampled.B;
_editingItem.RawData = data;
}
}
_editTextBox.Visible = false;
_editingItem = null;
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = e.Graphics; g.SmoothingMode = SmoothingMode.AntiAlias;
Rectangle rect = ClientRectangle;
_gradRect = new Rectangle(rect.X + 40, rect.Y + PaddingTopBottom, GradientWidth, rect.Height - (PaddingTopBottom * 2));
if (_fixedGradientBlend != null)
{
using (LinearGradientBrush brush = new LinearGradientBrush(_gradRect, Color.Black, Color.White, 90f))
{
brush.InterpolationColors = _fixedGradientBlend;
g.FillRectangle(brush, _gradRect);
}
g.DrawRectangle(Pens.DimGray, _gradRect);
foreach (var item in _items)
{
// 【关键】:反转时 Min 在顶Max 在底;正向时 Max 在顶Min 在底
if (_isReversed)
item.CurrentY = _gradRect.Top + (float)(item.NormalizedPosition * _gradRect.Height);
else
item.CurrentY = _gradRect.Bottom - (float)(item.NormalizedPosition * _gradRect.Height);
item.Bounds = new RectangleF(_gradRect.Right, item.CurrentY - 8, 150, 16);
item.TextBounds = new RectangleF(_gradRect.Right + 30, item.CurrentY - 8, 120, 16);
// 绘制横线色 (保持实时采样色)
float blendPos = _isReversed ? (float)item.NormalizedPosition : 1.0f - (float)item.NormalizedPosition;
Color sampledColor = GetInterpolatedColor(_fixedGradientBlend, blendPos);
using (Pen p = new Pen(sampledColor, item.IsHovered ? 2.5f : 1.5f))
{
g.DrawLine(p, _gradRect.Right, item.CurrentY, _gradRect.Right + 25, item.CurrentY);
}
if (_editingItem != item)
{
string label = item.RawData.z.ToString("F3");
Brush textBrush = item.IsSelected ? Brushes.Blue : (item.IsHovered ? Brushes.Red : Brushes.Black);
using (Font textFont = new Font(this.Font, item.IsSelected ? FontStyle.Bold : FontStyle.Regular))
{
g.DrawString(label, textFont, textBrush, item.TextBounds.X, item.TextBounds.Y);
}
}
}
}
}
private Color GetInterpolatedColor(ColorBlend blend, float pos)
{
if (pos <= blend.Positions[0]) return blend.Colors[0];
if (pos >= blend.Positions.Last()) return blend.Colors.Last();
for (int i = 1; i < blend.Positions.Length; i++)
{
if (pos < blend.Positions[i])
{
float t = (pos - blend.Positions[i - 1]) / (blend.Positions[i] - blend.Positions[i - 1]);
Color c1 = blend.Colors[i - 1], c2 = blend.Colors[i];
return Color.FromArgb((int)(c1.A + (c2.A - c1.A) * t), (int)(c1.R + (c2.R - c1.R) * t), (int)(c1.G + (c2.G - c1.G) * t), (int)(c1.B + (c2.B - c1.B) * t));
}
}
return blend.Colors.Last();
}
public List<ColorScaleEntry> GetResult() => _items.Select(i => i.RawData).OrderBy(d => d.z).ToList();
// 接收是否反转的参数 isReversed
public static List<ColorScaleEntry> ShowEditor(IWin32Window owner, IEnumerable<ColorScaleEntry> bgEntries, IEnumerable<ColorScaleEntry> markEntries, double min, double max, bool isReversed)
{
using (XtraForm form = new XtraForm())
{
form.Text = "色标编辑器";
form.Size = new Size(315, 600);
form.StartPosition = FormStartPosition.CenterParent;
form.FormBorderStyle = FormBorderStyle.FixedDialog;
form.MaximizeBox = false;
form.MinimizeBox = false;
var editor = new FrmGradientRulerEditor { Dock = DockStyle.Fill };
editor.SetData(bgEntries, markEntries, min, max, isReversed);
PanelControl pnlBottom = new PanelControl { Dock = DockStyle.Bottom, Height = 60 };
SimpleButton btnAdd = new SimpleButton { Text = "增加", Size = new Size(80, 30), Location = new Point(15, 15) };
SimpleButton btnDel = new SimpleButton { Text = "删除", Size = new Size(80, 30), Location = new Point(105, 15) };
SimpleButton btnOk = new SimpleButton { Text = "保存", Size = new Size(100, 30), Location = new Point(195, 15), DialogResult = DialogResult.OK };
btnAdd.Click += (s, e) => {
XtraInputBoxArgs args = new XtraInputBoxArgs();
args.Caption = "增加标注";
args.Prompt = "请输入Z值";
args.DefaultResponse = min.ToString("F3");
args.Buttons = new DialogResult[] { DialogResult.OK, DialogResult.Cancel };
var result = XtraInputBox.Show(args);
if (result != null && double.TryParse(result.ToString(), out double dVal))
{
editor.AddPointByValue(dVal);
}
};
btnDel.Click += (s, e) => editor.DeleteSelected();
pnlBottom.Controls.AddRange(new Control[] { btnAdd, btnDel, btnOk });
form.Controls.Add(editor);
form.Controls.Add(pnlBottom);
return (form.ShowDialog(owner) == DialogResult.OK) ? editor.GetResult() : null;
}
}
}
}