diff --git a/program/Language.cs b/program/Language.cs index a9afd86..e6a7bfc 100644 --- a/program/Language.cs +++ b/program/Language.cs @@ -10,6 +10,7 @@ private const string JpnCssPath = "lang_jpn.css"; private const string EngCsvPath = "info_eng.csv"; private const string JpnCsvPath = "info_jpn.csv"; + private const string TouchCssPath = "touch_friendly.css"; // NEW private readonly Dictionary _engText; private readonly Dictionary _jpnText; @@ -19,6 +20,7 @@ private readonly Dictionary _widgets = new Dictionary(); public readonly CssProvider _languageCssProvider = new CssProvider(); + private readonly CssProvider _touchCssProvider = new CssProvider(); // NEW public bool IsEnglish { get; private set; } @@ -27,6 +29,19 @@ public Language(bool isEnglish) { + // Touch-friendly sizing loads once, at a lower priority than the + // language CSS (600) so per-language #id rules can still override + // fonts/colors if needed, while touch sizing stays constant. + if (File.Exists(TouchCssPath)) + { + _touchCssProvider.LoadFromPath(TouchCssPath); + Console.WriteLine("Touch CSS loaded from: " + System.IO.Path.GetFullPath(TouchCssPath)); + } + else + { + Console.WriteLine("Warning: CSS file not found: " + TouchCssPath); + } + StyleContext.AddProviderForScreen(Gdk.Screen.Default, _touchCssProvider, 1000); StyleContext.AddProviderForScreen(Gdk.Screen.Default, _languageCssProvider, 600); _engText = LoadText(EngCsvPath); diff --git a/program/MapWindow.cs b/program/MapWindow.cs index 8a0a158..fcef215 100644 --- a/program/MapWindow.cs +++ b/program/MapWindow.cs @@ -229,38 +229,66 @@ // ARDUINO POSITION // ══════════════════════════════════════════════════════════════════ - private void OnArduinoLine(string line) + private void OnArduinoLine(string line) { - var avg = AvgRx.Match(line); - float x, y, z; + var fullMatches = FullRx.Matches(line); + var avgMatch = AvgRx.Match(line); - if (avg.Success) + float avgChipNumber = -1f; + float x = 0, y = 0, z = 0; + bool havePosition = false; + + _highlighted.Clear(); + + Logger.Write("chip", "raw='" + line + "' fullMatches=" + fullMatches.Count + // NEW + " avgMatch=" + avgMatch.Success); + + if (avgMatch.Success) { - x = ParseF(avg.Groups[1].Value); - y = ParseF(avg.Groups[2].Value); - z = ParseF(avg.Groups[3].Value); + avgChipNumber = ParseF(avgMatch.Groups[1].Value); + x = ParseF(avgMatch.Groups[2].Value); + y = ParseF(avgMatch.Groups[3].Value); + z = ParseF(avgMatch.Groups[4].Value); + havePosition = true; } - else + + if (fullMatches.Count > 0) { - var tag = TagRx.Match(line); - if (!tag.Success) return; // status/mode line — not a coordinate - x = ParseF(tag.Groups[2].Value); // group 1 is the tag number itself now - y = ParseF(tag.Groups[3].Value); - z = ParseF(tag.Groups[4].Value); + foreach (Match m in fullMatches) + { + float chipNumber = ParseF(m.Groups[1].Value); + if (Math.Abs(chipNumber - avgChipNumber) < 0.001f) continue; // that's the Avg entry itself + _highlighted.Add(chipNumber); // feeds the existing yellow-fill in DrawOneCircle + } + + if (!havePosition) + { + var first = fullMatches[0]; + x = ParseF(first.Groups[2].Value); + y = ParseF(first.Groups[3].Value); + z = ParseF(first.Groups[4].Value); + havePosition = true; + } } + if (!havePosition) return; // status/mode line, not a coordinate line + _currentX = x; _currentY = y; _currentZ = z; - _hasPosition = true; + _hasPosition = _highlighted.Count > 0; // NEW — marker only shows when a chip is actually on the target string newView = DetermineViewFromZ(z); if (newView != _view) SwitchViewFromArduino(newView); + // Logger.Write("chip", "highlighted=[" + string.Join(",", _highlighted) + "] " + // NEW + // "view=" + _view + " size=" + JacketSize + // NEW + // " circlesInView=" + (_jacket != null ? _jacket.Get(JacketSize, _view).Count : -1)); // NEW + + MapArea.QueueDraw(); - Logger.Write("pos", "line=" + line); - } + } private static float ParseF(string s) { @@ -312,9 +340,22 @@ double offsetX = (panelW - (maxX - minX) * scale) / 2 - minX * scale; double offsetY = (panelH - (maxY - minY) * scale) / 2 - minY * scale; - // Form2: raw Arduino units run ~50x smaller than CSV/circle units. - bx = (_currentX * 50 + t.MarginX) * scale + offsetX; - by = (_currentY * 50 + t.MarginY) * scale + offsetY; + // NEW — same centroid DrawCircles computes, needed so clustering below + // pulls the cursor toward the exact same point the circles cluster around. + double avgX = 0, avgY = 0; + foreach (var c in circles) + { + avgX += c.X * scale + offsetX - t.OffsetX; + avgY += c.Y * scale + offsetY - t.OffsetY; + } + avgX /= circles.Count; + avgY /= circles.Count; + + // CHANGED — subtract t.OffsetX/OffsetY, exactly like DrawOneCircle does. + // This was the main misalignment: circles were shifted by this offset, + // the cursor never was. + bx = (_currentX * 50 + t.MarginX) * scale + offsetX - t.OffsetX; + by = (_currentY * 50 + t.MarginY) * scale + offsetY - t.OffsetY; if (JacketSize == "XL" && (_view == "front" || _view == "right" || _view == "left")) { diff --git a/program/homepageallv6.glade b/program/homepageallv6.glade index 4630bf4..45b951d 100644 --- a/program/homepageallv6.glade +++ b/program/homepageallv6.glade @@ -4,6 +4,7 @@ False + False True @@ -435,20 +436,18 @@ 2 - + True False - horizontal 12 + 340 True True - 340 never - none True @@ -457,12 +456,12 @@ True False - vertical - 10 12 12 12 12 + vertical + 10 True @@ -473,13 +472,13 @@ True False - - - True - 0 8 8 6 6 + - + True + 0 @@ -506,11 +505,11 @@ True False - vertical 8 8 6 6 + vertical スケルトン @@ -607,11 +606,11 @@ True False - True 8 8 6 6 + True @@ -734,9 +733,9 @@ True False + True vertical 8 - True True @@ -773,12 +772,12 @@ True False - vertical - 6 - True 12 12 12 + True + vertical + 6 True @@ -818,7 +817,7 @@ 3 - + diff --git a/program/lang_eng.css b/program/lang_eng.css index fcf83ec..220280a 100644 --- a/program/lang_eng.css +++ b/program/lang_eng.css @@ -1,69 +1,6 @@ -#Option1Button { +#Option1Button, #Option2Button, #Option3Button, #CloseButton { font-family: 'Sans'; - font-size: 20px; - font-weight: normal; - font-style: normal; -} - -#Option2Button { - font-family: 'Sans'; - font-size: 20px; - font-weight: normal; - font-style: normal; -} - -#Option3Button { - font-family: 'Sans'; - font-size: 20px; - font-weight: normal; - font-style: normal; -} - -#CloseButton { - font-family: 'Sans'; - font-size: 18px; - font-weight: bold; - font-style: normal; } #StatusLabel.status-on { color: #2e7d32; font-weight: bold; } -#StatusLabel.status-off { color: #c62828; font-weight: bold; } - -/* Setting Window -#ConnectButton { - font-family: 'Sans'; - font-size: 20px; - font-weight: normal; - font-style: normal; -} - -#AutoConnectButton { - font-family: 'Sans'; - font-size: 20px; - font-weight: normal; - font-style: normal; -} - -#Option3Button { - font-family: 'Sans'; - font-size: 20px; - font-weight: normal; - font-style: normal; -} - -#CloseButton { - font-family: 'Sans'; - font-size: 18px; - font-weight: bold; - font-style: normal; -} */ - -/* [UI] private Label ComPortLabel = null; - [UI] private ComboBoxText ComPortComboBox = null; - [UI] private Button ConnectButton = null; - [UI] private Button AutoConnectButton = null; - [UI] private Button RefreshButton = null; - [UI] private Button BackButton = null; - [UI] private Label StatusLabel = null; - [UI] private Label LogLabel = null; - [UI] private TextView LogtextBox = null; */ \ No newline at end of file +#StatusLabel.status-off { color: #c62828; font-weight: bold; } \ No newline at end of file diff --git a/program/lang_jpn.css b/program/lang_jpn.css index 13b87e2..dcc6859 100644 --- a/program/lang_jpn.css +++ b/program/lang_jpn.css @@ -1,29 +1,5 @@ -#Option1Button { +#Option1Button, #Option2Button, #Option3Button, #CloseButton { font-family: 'Noto Sans JP'; - font-size: 20px; - font-weight: normal; - font-style: normal; -} - -#Option2Button { - font-family: 'Noto Sans JP'; - font-size: 20px; - font-weight: normal; - font-style: normal; -} - -#Option3Button { - font-family: 'Noto Sans JP'; - font-size: 20px; - font-weight: normal; - font-style: normal; -} - -#CloseButton { - font-family: 'Noto Sans JP'; - font-size: 18px; - font-weight: bold; - font-style: normal; } #StatusLabel.status-on { color: #2e7d32; font-weight: bold; } diff --git a/program/touch_friendly.css b/program/touch_friendly.css new file mode 100644 index 0000000..3056dd5 --- /dev/null +++ b/program/touch_friendly.css @@ -0,0 +1,148 @@ +/* touch_friendly.css — larger fonts */ + +window { + background-color: #f7f7f9; +} + +label, checkbutton, radiobutton, combobox { + font-family: 'Segoe UI', 'Noto Sans', sans-serif; + color: #1e1e1e; + font-size: 20px; +} + +/* Splash page: big mode-select buttons */ +#Option1Button, #Option2Button, #Option3Button { + font-size: 34px; /* was 26px */ + font-weight: 700; + border-radius: 16px; + background-color: #ffffff; + border: 1px solid #d8d8de; + box-shadow: 0 2px 4px rgba(0,0,0,0.08); +} +#Option1Button:hover, #Option2Button:hover, #Option3Button:hover { + background-color: #eef3ff; + border-color: #a9c3ff; +} +#Option1Button:active, #Option2Button:active, #Option3Button:active { + background-color: #dbe6ff; +} + +#SettingButton, #CloseButton { + min-height: 56px; + min-width: 120px; + font-size: 24px; /* was 18px */ + font-weight: 600; + border-radius: 10px; + padding: 8px 18px; +} +#CloseButton { + background-color: #fdeaea; + border: 1px solid #f2b8b8; + color: #a12727; +} +#CloseButton:hover { background-color: #fbd6d6; } + +#LanguageToggle { + min-height: 52px; + min-width: 76px; + font-size: 24px; /* was 18px */ + border-radius: 26px; + padding: 6px 14px; +} + +#ConnectButton, #AutoConnectButton, #RefreshButton, #SettingBackButton { + min-height: 52px; + min-width: 160px; + font-size: 22px; /* was 18px */ + font-weight: 600; + border-radius: 10px; + padding: 8px 20px; +} +#ComPortComboBox { + min-height: 50px; + font-size: 22px; + padding: 6px 12px; + border-radius: 8px; +} +#StatusLabel.status-on { color: #2e7d32; font-weight: bold; } +#StatusLabel.status-off { color: #c62828; font-weight: bold; } + +#SoundBackButton, #SoundTestButton { + min-height: 52px; + min-width: 140px; + font-size: 22px; + font-weight: 600; + border-radius: 10px; + padding: 8px 20px; +} + +.sound-clip { + min-height: 72px; + min-width: 140px; + font-size: 22px; + font-weight: 600; + border-radius: 12px; + padding: 10px 16px; +} + +#MapBackButton { + min-height: 52px; + min-width: 140px; + font-size: 22px; + font-weight: 600; + border-radius: 10px; + padding: 8px 20px; +} + +#CaseFrame > label, #BodyFrame > label, #ViewFrame > label { + font-size: 19px; + font-weight: 700; +} +#MapCaseLabel { + font-size: 21px; +} + +#BodySkeletonRadio, #BodyJacketRadio { + font-size: 21px; + min-height: 46px; +} +#BodySkeletonRadio radio, #BodyJacketRadio radio { + min-width: 30px; + min-height: 30px; +} + +#ViewFrontRadio, #ViewBackRadio, #ViewRightRadio, #ViewLeftRadio { + min-height: 52px; + font-size: 21px; + font-weight: 600; + border-radius: 8px; + padding: 8px 4px; +} +#ViewFrontRadio:checked, #ViewBackRadio:checked, +#ViewRightRadio:checked, #ViewLeftRadio:checked { + background-color: #2e7d32; + color: white; +} + +#HideCircleCheck, #BlackAudioCheck { + font-size: 20px; + min-height: 44px; +} +#HideCircleCheck check, #BlackAudioCheck check { + min-width: 30px; + min-height: 30px; +} + +#JacketSizeCombo { + min-height: 48px; + font-size: 20px; + border-radius: 8px; +} +#JacketSizeLabel { + font-size: 20px; +} + +button { + min-height: 48px; + padding: 10px 18px; +} \ No newline at end of file