Dreamine.UI.WinForms 1.0.1
Dreamine.UI.WinForms 사용자 인터페이스 기능과 구성 요소를 제공합니다.
로딩중...
검색중...
일치하는것 없음
DreamineVirtualKeyboardForm.cs
이 파일의 문서화 페이지로 가기
1using System.Drawing;
2using System.Windows.Forms;
4
6
15internal sealed class DreamineVirtualKeyboardForm : Form
16{
25 private static readonly Color KeyBackground = Color.FromArgb(0xF1, 0x68, 0x5E);
34 private static readonly Color KeySelectedBackground = Color.FromArgb(0xF5, 0x95, 0x84);
43 private const int KeyHeight = 54;
52 private const int KeyMargin = 2;
61 private const int RowHeight = 58;
70 private const int TextKeyboardWidth = 928;
71
80 private readonly DreamineTextBox _target;
89 private readonly VkLayout _layout;
98 private readonly HangulComposer _hangulComposer = new();
107 private readonly List<KeyButton> _keyButtons = [];
116 private readonly System.Windows.Forms.Timer _stateTimer;
117
126 private bool _shift;
135 private bool _physicalShift;
144 private bool _korean;
153 private DreamineButton? _shiftButton;
162 private DreamineButton? _capsButton;
171 private DreamineButton? _languageButton;
180 private int _contentWidth;
189 private int _contentHeight;
190
223 public DreamineVirtualKeyboardForm(DreamineTextBox target, VkLayout layout)
224 {
225 _target = target;
226 _layout = layout;
227 _korean = IsKoreanInputMode();
228
229 FormBorderStyle = FormBorderStyle.None;
230 StartPosition = FormStartPosition.Manual;
231 ShowInTaskbar = false;
232 TopMost = true;
233 BackColor = Color.Black;
234 Padding = new Padding(18, 14, 18, 14);
235
236 Controls.Add(layout == VkLayout.Numeric ? BuildNumericLayout() : BuildTextLayout());
237
238 _stateTimer = new System.Windows.Forms.Timer { Interval = 180 };
239 _stateTimer.Tick += (_, _) => SyncPhysicalKeyboardState();
240 _stateTimer.Start();
241
242 RefreshKeys();
243
244 var contentWidth = _contentWidth == 0 ? 900 : _contentWidth;
245 var contentHeight = _contentHeight == 0 ? (layout == VkLayout.Numeric ? 4 : 5) * RowHeight : _contentHeight;
246 ClientSize = new Size(contentWidth + Padding.Horizontal, contentHeight + Padding.Vertical);
247 }
248
265 private Control BuildNumericLayout()
266 {
267 var root = CreateRootPanel();
268 AddRow(root, [KeySpec.Text("1"), KeySpec.Text("2"), KeySpec.Text("3"), KeySpec.Command("Backspace", 104, Backspace)]);
269 AddRow(root, [KeySpec.Text("4"), KeySpec.Text("5"), KeySpec.Text("6"), KeySpec.Command("Enter", 104, Close)]);
270 AddRow(root, [KeySpec.Text("7"), KeySpec.Text("8"), KeySpec.Text("9"), KeySpec.Text(".")]);
271 AddRow(root, [KeySpec.Text("0", 148), KeySpec.Command("Clear", 148, Clear)]);
272 return root;
273 }
274
291 private Control BuildTextLayout()
292 {
293 var root = CreateRootPanel();
294
295 AddRow(root,
296 [
297 KeySpec.Command("Esc", 54, Close),
298 KeySpec.Text("`"), KeySpec.Text("1"), KeySpec.Text("2"), KeySpec.Text("3"), KeySpec.Text("4"),
299 KeySpec.Text("5"), KeySpec.Text("6"), KeySpec.Text("7"), KeySpec.Text("8"), KeySpec.Text("9"),
300 KeySpec.Text("0"), KeySpec.Text("-"), KeySpec.Text("="),
301 KeySpec.Command("Backspace", 116, Backspace)
302 ], TextKeyboardWidth, 14);
303
304 AddRow(root,
305 [
306 KeySpec.Command("Tab", 100, () => InsertRaw(" ")),
307 KeySpec.Text("q"), KeySpec.Text("w"), KeySpec.Text("e"), KeySpec.Text("r"), KeySpec.Text("t"),
308 KeySpec.Text("y"), KeySpec.Text("u"), KeySpec.Text("i"), KeySpec.Text("o"), KeySpec.Text("p"),
309 KeySpec.Text("["), KeySpec.Text("]"), KeySpec.Text("\\", 100)
310 ], TextKeyboardWidth, 13);
311
312 AddRow(root,
313 [
314 KeySpec.Command("Caps Lock", 116, ToggleCapsLock),
315 KeySpec.Text("a"), KeySpec.Text("s"), KeySpec.Text("d"), KeySpec.Text("f"), KeySpec.Text("g"),
316 KeySpec.Text("h"), KeySpec.Text("j"), KeySpec.Text("k"), KeySpec.Text("l"),
317 KeySpec.Text(";"), KeySpec.Text("'"),
318 KeySpec.Command("Enter", 116, Close)
319 ], TextKeyboardWidth, 0, 12);
320
321 AddRow(root,
322 [
323 KeySpec.Command("Shift", 138, ToggleShift),
324 KeySpec.Text("z"), KeySpec.Text("x"), KeySpec.Text("c"), KeySpec.Text("v"), KeySpec.Text("b"),
325 KeySpec.Text("n"), KeySpec.Text("m"), KeySpec.Text(","), KeySpec.Text("."), KeySpec.Text("/"),
326 KeySpec.Command("◀", 58, MoveLeft),
327 KeySpec.Command("▶", 58, MoveRight)
328 ], TextKeyboardWidth, 0, 11, 12);
329
330 AddRow(root,
331 [
332 KeySpec.Command("Ctrl", 124, () => { }),
333 KeySpec.Command("Space", 696, () => InsertRaw(" ")),
334 KeySpec.Command("abc", 72, ToggleLanguage)
335 ], TextKeyboardWidth, 1);
336
337 return root;
338 }
339
356 private FlowLayoutPanel CreateRootPanel()
357 {
358 return new FlowLayoutPanel
359 {
360 Dock = DockStyle.Fill,
361 FlowDirection = FlowDirection.TopDown,
362 WrapContents = false,
363 BackColor = Color.Black,
364 Margin = Padding.Empty,
365 Padding = Padding.Empty,
366 AutoScroll = false,
367 };
368 }
369
410 private void AddRow(FlowLayoutPanel root, IReadOnlyList<KeySpec> specs, int? targetWidth = null, params int[] expandIndexes)
411 {
412 var arrangedSpecs = specs.ToArray();
413 var rowWidth = arrangedSpecs.Sum(spec => spec.Width + (KeyMargin * 2));
414 if (targetWidth is { } width && expandIndexes.Length > 0 && rowWidth < width)
415 {
416 var validIndexes = expandIndexes
417 .Where(index => index >= 0 && index < arrangedSpecs.Length)
418 .Distinct()
419 .ToArray();
420
421 if (validIndexes.Length > 0)
422 {
423 var extra = width - rowWidth;
424 var share = extra / validIndexes.Length;
425 var remainder = extra % validIndexes.Length;
426
427 for (var i = 0; i < validIndexes.Length; i++)
428 {
429 var index = validIndexes[i];
430 var spec = arrangedSpecs[index];
431 arrangedSpecs[index] = spec with { Width = spec.Width + share + (i == validIndexes.Length - 1 ? remainder : 0) };
432 }
433 }
434
435 rowWidth = width;
436 }
437
438 var row = new FlowLayoutPanel
439 {
440 Width = rowWidth,
441 Height = RowHeight,
442 FlowDirection = FlowDirection.LeftToRight,
443 WrapContents = false,
444 BackColor = Color.Black,
445 Margin = Padding.Empty,
446 Padding = Padding.Empty,
447 };
448
449 foreach (var spec in arrangedSpecs)
450 {
451 var button = MakeKey(spec);
452 row.Controls.Add(button);
453 }
454
455 root.Controls.Add(row);
456 _contentWidth = Math.Max(_contentWidth, rowWidth);
457 _contentHeight += RowHeight;
458 }
459
484 private DreamineButton MakeKey(KeySpec spec)
485 {
486 var button = new DreamineButton
487 {
488 Content = spec.Label,
489 Width = spec.Width,
490 Height = KeyHeight,
491 CornerRadius = 4,
492 Margin = new Padding(KeyMargin),
493 BackColor = KeyBackground,
494 ForeColor = Color.White,
495 BorderColor = KeyBackground,
496 Font = new Font("Segoe UI", spec.Width > 90 ? 12f : 14f, FontStyle.Regular, GraphicsUnit.Point)
497 };
498
499 if (spec.Kind == KeyKind.Text)
500 _keyButtons.Add(new KeyButton(button, spec.Label));
501
502 if (spec.Label == "Shift")
503 _shiftButton = button;
504 else if (spec.Label == "Caps Lock")
505 _capsButton = button;
506 else if (spec.Label is "abc" or "가")
507 _languageButton = button;
508
509 button.MouseUp += (_, e) =>
510 {
511 if (e.Button != MouseButtons.Left || !button.ClientRectangle.Contains(e.Location))
512 return;
513
514 if (_target.IsDisposed)
515 {
516 Close();
517 return;
518 }
519
520 _target.Focus();
521 if (spec.Kind == KeyKind.Text)
522 InsertKey(spec.Label);
523 else
524 spec.Action?.Invoke();
525 };
526
527 return button;
528 }
529
546 private void InsertKey(string key)
547 {
548 var text = GetKeyText(key);
549 if (_korean && HangulComposer.IsComposableJamo(text))
550 {
551 var edit = _hangulComposer.Input(text, _target.GetTextBeforeCaret());
552 _target.ReplaceTextTail(edit.ReplaceCount, edit.Text);
553 }
554 else
555 {
556 _hangulComposer.Reset();
557 _target.InsertText(text);
558 }
559
560 if (_shift)
561 {
562 _shift = false;
563 RefreshKeys();
564 }
565 }
566
583 private void InsertRaw(string text)
584 {
585 _hangulComposer.Reset();
586 _target.InsertText(text);
587 }
588
597 private void Backspace()
598 {
599 _hangulComposer.Reset();
600 _target.Backspace();
601 }
602
611 private void Clear()
612 {
613 _hangulComposer.Reset();
614 _target.Text = string.Empty;
615 }
616
625 private void ToggleShift()
626 {
627 _shift = !_shift;
628 RefreshKeys();
629 }
630
639 private void ToggleCapsLock()
640 {
641 _hangulComposer.Reset();
642 SendKeys.SendWait("{CAPSLOCK}");
643 RefreshKeys();
644 }
645
654 private void ToggleLanguage()
655 {
656 _hangulComposer.Reset();
657 _korean = !_korean;
658 ApplyInputLanguage(_korean);
659 ImeHelper.SetNativeMode(_target.TextBoxHandle, _korean);
660 RefreshKeys();
661 }
662
671 private void MoveLeft()
672 {
673 _target.SelectionStart = Math.Max(0, _target.SelectionStart - 1);
674 }
675
684 private void MoveRight()
685 {
686 _target.SelectionStart = Math.Min(_target.Text.Length, _target.SelectionStart + 1);
687 }
688
697 private void SyncPhysicalKeyboardState()
698 {
699 if (IsDisposed)
700 return;
701
702 var caps = Control.IsKeyLocked(Keys.CapsLock);
703 _physicalShift = (ModifierKeys & Keys.Shift) == Keys.Shift;
704 _korean = IsKoreanInputMode();
705 if (_capsButton != null)
706 _capsButton.IsSelected = caps;
707
708 RefreshKeys();
709 }
710
719 private void RefreshKeys()
720 {
721 var caps = Control.IsKeyLocked(Keys.CapsLock);
722
723 foreach (var key in _keyButtons)
724 key.Button.Content = GetKeyText(key.BaseText);
725
726 if (_shiftButton != null)
727 {
728 _shiftButton.IsSelected = EffectiveShift;
729 _shiftButton.BackColor = EffectiveShift ? KeySelectedBackground : KeyBackground;
730 }
731
732 if (_capsButton != null)
733 {
734 _capsButton.IsSelected = caps;
735 _capsButton.BackColor = caps ? KeySelectedBackground : KeyBackground;
736 }
737
738 if (_languageButton != null)
739 {
740 _languageButton.Content = _korean ? "가" : "abc";
741 _languageButton.IsSelected = _korean;
742 _languageButton.BackColor = _korean ? KeySelectedBackground : KeyBackground;
743 }
744 }
745
778 private string GetKeyText(string key)
779 {
780 if (_korean && KoreanKeys.TryGetValue(key, out var korean))
781 return EffectiveShift && KoreanShiftKeys.TryGetValue(key, out var koreanShift) ? koreanShift : korean;
782
783 if (ShiftKeys.TryGetValue(key, out var shifted) && EffectiveShift)
784 return shifted;
785
786 if (key.Length == 1 && char.IsLetter(key[0]))
787 {
788 var upper = EffectiveShift ^ Control.IsKeyLocked(Keys.CapsLock);
789 return upper ? key.ToUpperInvariant() : key.ToLowerInvariant();
790 }
791
792 return key;
793 }
794
811 protected override void OnFormClosed(FormClosedEventArgs e)
812 {
813 _stateTimer.Stop();
814 _stateTimer.Dispose();
815 base.OnFormClosed(e);
816 }
817
834 protected override void OnPaint(PaintEventArgs e)
835 {
836 base.OnPaint(e);
837 using var pen = new Pen(Color.Gray, 4f);
838 e.Graphics.DrawRectangle(pen, 1, 1, Width - 3, Height - 3);
839 }
840
849 protected override bool ShowWithoutActivation => true;
850
859 private bool EffectiveShift => _shift || _physicalShift;
860
877 private bool IsKoreanInputMode()
878 {
879 return InputLanguage.CurrentInputLanguage.Culture.Name.Equals("ko-KR", StringComparison.OrdinalIgnoreCase) &&
880 ImeHelper.IsNativeMode(_target.TextBoxHandle);
881 }
882
899 private static void ApplyInputLanguage(bool korean)
900 {
901 var cultureName = korean ? "ko-KR" : "en-US";
902 foreach (InputLanguage language in InputLanguage.InstalledInputLanguages)
903 {
904 if (language.Culture.Name.Equals(cultureName, StringComparison.OrdinalIgnoreCase))
905 {
906 InputLanguage.CurrentInputLanguage = language;
907 return;
908 }
909 }
910 }
911
920 private static readonly Dictionary<string, string> ShiftKeys = new()
921 {
922 ["`"] = "~", ["1"] = "!", ["2"] = "@", ["3"] = "#", ["4"] = "$",
923 ["5"] = "%", ["6"] = "^", ["7"] = "&", ["8"] = "*", ["9"] = "(",
924 ["0"] = ")", ["-"] = "_", ["="] = "+", ["["] = "{", ["]"] = "}",
925 ["\\"] = "|", [";"] = ":", ["'"] = "\"", [","] = "<", ["."] = ">",
926 ["/"] = "?",
927 };
928
937 private static readonly Dictionary<string, string> KoreanKeys = new()
938 {
939 ["q"] = "ㅂ", ["w"] = "ㅈ", ["e"] = "ㄷ", ["r"] = "ㄱ", ["t"] = "ㅅ",
940 ["y"] = "ㅛ", ["u"] = "ㅕ", ["i"] = "ㅑ", ["o"] = "ㅐ", ["p"] = "ㅔ",
941 ["a"] = "ㅁ", ["s"] = "ㄴ", ["d"] = "ㅇ", ["f"] = "ㄹ", ["g"] = "ㅎ",
942 ["h"] = "ㅗ", ["j"] = "ㅓ", ["k"] = "ㅏ", ["l"] = "ㅣ",
943 ["z"] = "ㅋ", ["x"] = "ㅌ", ["c"] = "ㅊ", ["v"] = "ㅍ", ["b"] = "ㅠ",
944 ["n"] = "ㅜ", ["m"] = "ㅡ",
945 };
946
955 private static readonly Dictionary<string, string> KoreanShiftKeys = new()
956 {
957 ["q"] = "ㅃ", ["w"] = "ㅉ", ["e"] = "ㄸ", ["r"] = "ㄲ", ["t"] = "ㅆ",
958 ["o"] = "ㅒ", ["p"] = "ㅖ",
959 };
960
985 private sealed record KeyButton(DreamineButton Button, string BaseText);
986
1027 private sealed record KeySpec(KeyKind Kind, string Label, int Width, Action? Action)
1028 {
1061 public static KeySpec Text(string label, int width = 54) => new(KeyKind.Text, label, width, null);
1103 public static KeySpec Command(string label, int width, Action action) => new(KeyKind.Command, label, width, action);
1104 }
1105
1114 private enum KeyKind
1115 {
1124 Text,
1133 Command
1134 }
1135}