From 86a4ed79270f11bfe695e4c657927b7bc962ff81 Mon Sep 17 00:00:00 2001 From: Hermes Date: Mon, 20 Apr 2026 20:32:06 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=BC=B9=E5=87=BA=E5=BC=8F=E9=A2=9C?= =?UTF-8?q?=E8=89=B2=E9=80=89=E6=8B=A9=E5=99=A8=20+=20=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=E9=A2=84=E8=A7=88=E6=97=81=E9=A2=9C=E8=89=B2=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - showColorPickerPopup: 新增独立弹窗颜色选择器,支持最近使用颜色(8个)、RGB滑块实时调色、常用色网格、透明度滑块 - 图标预览区实时反馏,确定后自动保存到最近列表 - 在 ShortX 图标预览卡片旁新增“颜色”按钮,直接打开颜色选择器 - 更新 README 文档 - 修复调色板卡片未加入 form 的问题 --- README.md | 30 +++++-- code/th_14_panels.js | 193 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 201 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index dcfbc54..82650cb 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,6 @@ ToolHub/ 按钮编辑页里的 ShortX 图标选择器现已改为: - **ShortX 图标名称编辑框已取消**,改为预览卡片 + 图标库点选 -- **图标颜色支持折叠式完整调色板**,支持常用色、最近 5 色、透明度调节、RGB 三色调色器(0-255),并可切回跟随主题 - **分页模式**,不再一次性塞入大批图标 - **图标列表按当前可用宽度自动排列列数,并结合可见高度计算每页容量** - 保留 **搜索 / 分类 / 上一页 / 下一页** @@ -138,12 +137,28 @@ ToolHub/ - 收起后再次点击 **展开图标库** 可正常重新打开 当前交互要点: -1. 图标库与调色板默认收起,点击 **展开图标库 / 展开调色板** 后打开 -2. 常用颜色会按色相自动排序,主题色固定在最前,并像 ShortX 图标列表一样按当前可用宽度自动排布 -3. 列数按当前可用宽度自动计算,屏幕更宽时一页可显示更多图标 -4. 每页容量按自动列数 × 当前可见行数实时计算 -5. 搜索、切分类、翻页时都会回到顶部,减少卡顿感和误触 -6. 选中图标后自动回填并收起图标库 +1. 图标库默认收起,点击 **展开图标库** 后打开 +2. 列数按当前可用宽度自动计算,屏幕更宽时一页可显示更多图标 +3. 每页容量按自动列数 × 当前可见行数实时计算 +4. 搜索、切分类、翻页时都会回到顶部,减少卡顿感和误触 +5. 选中图标后自动回填并收起图标库 + +## 弹出式颜色选择器 + +点击 **选择颜色** 按钮弹出独立颜色选择器弹窗: +- **图标预览区**:实时显示当前图标着色效果 +- **最近使用**:最多 8 个,点击直接复用,自动去重并置顶 +- **常用颜色**:21 色固定网格(7 列),点击直接选中 +- **RGB 三色调色器**:红/绿/蓝各 0-255 拖动进度条,实时同步预览 +- **透明度滑块**:A 通道 0-255,支持半透明图标着色 +- **清空按钮**:一键恢复跟随主题 +- **确定按钮**:确认选择并将颜色加入最近使用列表 + +交互要点: +1. 拖动 RGB 滑块时,预览图标实时变色,当前颜色值文本实时更新 +2. 点击常用色或最近色后,RGB 滑块自动同步到对应值 +3. 清空后滑块重置为 255/255/255/255(白/不透明) +4. 最近颜色持久化存储,跨会话保留 --- @@ -208,3 +223,4 @@ shortx.getShortXDir() + "/ToolHub/logs/init.log" - 优化入口返回信息格式 - **设置面板改造**:取消“执行与查看器”、“悬浮球文字/大小/颜色”设置项 - **悬浮球图标配置** 支持 ShortX 图标选择器(弹窗列表+搜索)和调色板拖拽选色 +- **弹出式颜色选择器**:新增独立弹窗,支持最近使用颜色(8 个)、RGB 实时调色、常用色网格、透明度滑块,最近颜色持久化存储 diff --git a/code/th_14_panels.js b/code/th_14_panels.js index ac50234..ab44a6f 100644 --- a/code/th_14_panels.js +++ b/code/th_14_panels.js @@ -914,7 +914,7 @@ FloatBallAppWM.prototype.buildButtonEditorPanelView = function() { shortxBtnGap2.setLayoutParams(new android.widget.LinearLayout.LayoutParams(self.dp(8), 1)); shortxQuickRow.addView(shortxBtnGap2); - var btnClearShortXIcon = self.ui.createFlatButton(self, "清空", subTextColor, function() { + var btnClearShortXIcon = self.ui.createFlatButton(self, "\u6e05\u7a7a", subTextColor, function() { self.touchActivity(); currentShortXIconName = ""; updateShortXIconPreview(); @@ -922,6 +922,26 @@ FloatBallAppWM.prototype.buildButtonEditorPanelView = function() { shortxPickerState.clearBtn = btnClearShortXIcon; shortxQuickRow.addView(btnClearShortXIcon); + var shortxBtnGap3 = new android.view.View(context); + shortxBtnGap3.setLayoutParams(new android.widget.LinearLayout.LayoutParams(self.dp(8), 1)); + shortxQuickRow.addView(shortxBtnGap3); + + var btnColorShortX = self.ui.createFlatButton(self, "\u989c\u8272", C.primary, function() { + self.touchActivity(); + var currentTint = (inputShortXIconTint && inputShortXIconTint.input) ? String(inputShortXIconTint.input.getText() || "") : ""; + self.showColorPickerPopup({ + currentColor: currentTint, + currentIconName: currentShortXIconName, + onSelect: function(colorHex) { + if (inputShortXIconTint && inputShortXIconTint.input) { + inputShortXIconTint.input.setText(colorHex); + } + try { if (tintPaletteState.toggleBtn) tintPaletteState.toggleBtn.setText(colorHex || "\u9009\u62e9\u989c\u8272"); } catch(e) {} + } + }); + }); + shortxQuickRow.addView(btnColorShortX); + var shortxPickerWrap = new android.widget.LinearLayout(context); shortxPickerWrap.setOrientation(android.widget.LinearLayout.VERTICAL); shortxPickerWrap.setPadding(0, 0, 0, self.dp(8)); @@ -1449,8 +1469,7 @@ FloatBallAppWM.prototype.buildButtonEditorPanelView = function() { tintPaletteWrap.setOrientation(android.widget.LinearLayout.VERTICAL); tintPaletteWrap.setPadding(0, 0, 0, self.dp(12)); tintPaletteWrap.setBackground(self.ui.createRoundDrawable(self.withAlpha(cardColor, 0.92), self.dp(14))); - // [Popup] tint palette moved to showColorPickerPopup() — no longer embedded - // form.addView(tintPaletteWrap); + form.addView(tintPaletteWrap); tintPaletteState.pickerWrap = tintPaletteWrap; var tintPaletteHead = new android.widget.LinearLayout(context); @@ -4232,6 +4251,43 @@ FloatBallAppWM.prototype.showColorPickerPopup = function(opts) { "#FFE91E63", "#FF795548", "#FF9E9E9E", "#FF607D8B", "#FF000000", "#FFFFFFFF" ]; + // ========== 最近使用颜色 ========== + var RECENT_COLORS_KEY = "color_picker_recent"; + var MAX_RECENT_COLORS = 8; + var recentColors = []; + try { + var recentSaved = self.loadPanelState(RECENT_COLORS_KEY); + if (recentSaved && recentSaved.colors && recentSaved.colors.length) { + var rc; + for (rc = 0; rc < recentSaved.colors.length && rc < MAX_RECENT_COLORS; rc++) { + var rn = normalizeTintColorValue(recentSaved.colors[rc], false); + if (rn) recentColors.push(rn); + } + } + } catch(eRecentLoad) {} + + function saveRecentColors() { + try { + self.savePanelState(RECENT_COLORS_KEY, { colors: recentColors.slice(0, MAX_RECENT_COLORS) }); + } catch(eRecentSave) {} + } + + function pushRecentColor(hex) { + var normalized = normalizeTintColorValue(hex, false); + if (!normalized) return; + var next = [normalized]; + var i; + for (i = 0; i < recentColors.length; i++) { + if (recentColors[i] !== normalized) { + next.push(recentColors[i]); + } + if (next.length >= MAX_RECENT_COLORS) break; + } + recentColors = next; + saveRecentColors(); + refreshRecentGrid(); + } + var selectedColor = currentColor; var isFollowTheme = !currentColor; var currentBaseRgbHex = extractTintRgbHex(currentColor); @@ -4284,7 +4340,97 @@ FloatBallAppWM.prototype.showColorPickerPopup = function(opts) { } updatePreview(); - // 21 色快捷网格 + // ========== 最近使用颜色 ========== + var recentTitle = new android.widget.TextView(context); + recentTitle.setText("最近使用"); + recentTitle.setTextColor(subTextColor); + recentTitle.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12); + recentTitle.setPadding(self.dp(12), self.dp(8), self.dp(12), self.dp(4)); + content.addView(recentTitle); + + var recentGrid = new android.widget.GridLayout(context); + recentGrid.setColumnCount(8); + recentGrid.setPadding(self.dp(8), self.dp(4), self.dp(8), self.dp(4)); + content.addView(recentGrid); + + var recentEmptyTv = new android.widget.TextView(context); + recentEmptyTv.setText("暂无最近颜色"); + recentEmptyTv.setTextColor(subTextColor); + recentEmptyTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 11); + recentEmptyTv.setPadding(self.dp(12), self.dp(4), self.dp(12), self.dp(8)); + recentEmptyTv.setVisibility(android.view.View.GONE); + content.addView(recentEmptyTv); + + function refreshRecentGrid() { + try { + recentGrid.removeAllViews(); + if (!recentColors.length) { + recentEmptyTv.setVisibility(android.view.View.VISIBLE); + return; + } + recentEmptyTv.setVisibility(android.view.View.GONE); + var ri; + for (ri = 0; ri < recentColors.length && ri < MAX_RECENT_COLORS; ri++) { + (function(hex) { + var cell = new android.widget.FrameLayout(context); + var margin = self.dp(3); + try { + var lp = new android.widget.GridLayout.LayoutParams(); + lp.width = self.dp(28); + lp.height = self.dp(28); + lp.setMargins(margin, margin, margin, margin); + cell.setLayoutParams(lp); + } catch(e) {} + + var swatch = new android.view.View(context); + swatch.setLayoutParams(new android.widget.FrameLayout.LayoutParams(android.widget.FrameLayout.LayoutParams.MATCH_PARENT, android.widget.FrameLayout.LayoutParams.MATCH_PARENT)); + try { + var bg = new android.graphics.drawable.GradientDrawable(); + bg.setColor(android.graphics.Color.parseColor(hex)); + bg.setCornerRadius(self.dp(5)); + swatch.setBackground(bg); + } catch(e) {} + cell.addView(swatch); + + if (selectedColor === hex) { + try { + var border = new android.graphics.drawable.GradientDrawable(); + border.setColor(android.graphics.Color.TRANSPARENT); + border.setCornerRadius(self.dp(5)); + border.setStroke(self.dp(2), C.primary); + cell.setForeground(border); + } catch(e) {} + } + + cell.setOnClickListener(new android.view.View.OnClickListener({ + onClick: function() { + self.touchActivity(); + isFollowTheme = false; + selectedColor = hex; + currentBaseRgbHex = extractTintRgbHex(hex); + currentAlphaByte = extractTintAlphaByte(hex); + updatePreview(); + updateValueTv(); + refreshRecentGrid(); + refreshCommonGrid(); + syncRgbSeeks(); + } + })); + recentGrid.addView(cell); + })(recentColors[ri]); + } + } catch(e) {} + } + refreshRecentGrid(); + + // 21 色常用颜色 + var commonTitle = new android.widget.TextView(context); + commonTitle.setText("常用颜色"); + commonTitle.setTextColor(subTextColor); + commonTitle.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12); + commonTitle.setPadding(self.dp(12), self.dp(8), self.dp(12), self.dp(4)); + content.addView(commonTitle); + var commonGrid = new android.widget.GridLayout(context); commonGrid.setColumnCount(7); commonGrid.setPadding(self.dp(8), self.dp(4), self.dp(8), self.dp(8)); @@ -4292,7 +4438,6 @@ FloatBallAppWM.prototype.showColorPickerPopup = function(opts) { for (ci = 0; ci < commonTintHexValues.length; ci++) { (function(hex) { var cell = new android.widget.FrameLayout(context); - cell.setLayoutParams(new android.widget.GridLayout.LayoutParams(self.dp(32), self.dp(32))); var margin = self.dp(4); try { var lp = new android.widget.GridLayout.LayoutParams(); @@ -4331,7 +4476,9 @@ FloatBallAppWM.prototype.showColorPickerPopup = function(opts) { currentAlphaByte = extractTintAlphaByte(hex); updatePreview(); updateValueTv(); + refreshRecentGrid(); refreshCommonGrid(); + syncRgbSeeks(); } })); commonGrid.addView(cell); @@ -4348,7 +4495,6 @@ FloatBallAppWM.prototype.showColorPickerPopup = function(opts) { if (!cell) continue; try { cell.setForeground(null); } catch(e) {} } - // 找到匹配的子项设置边框 var idx = commonTintHexValues.indexOf(selectedColor); if (idx >= 0 && idx < count) { var matchedCell = commonGrid.getChildAt(idx); @@ -4380,6 +4526,7 @@ FloatBallAppWM.prototype.showColorPickerPopup = function(opts) { // RGB 滑块 var rgbLabels = ["R", "G", "B"]; var rgbSeeks = []; + var rgbValTvs = []; var ri; for (ri = 0; ri < 3; ri++) { (function(idx) { @@ -4408,6 +4555,7 @@ FloatBallAppWM.prototype.showColorPickerPopup = function(opts) { valTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 11); valTv.setMinWidth(self.dp(28)); row.addView(valTv); + rgbValTvs.push(valTv); seek.setOnSeekBarChangeListener(new android.widget.SeekBar.OnSeekBarChangeListener({ onProgressChanged: function(s, progress, fromUser) { @@ -4422,6 +4570,7 @@ FloatBallAppWM.prototype.showColorPickerPopup = function(opts) { selectedColor = buildArgbHex(currentAlphaByte, currentBaseRgbHex); updatePreview(); updateValueTv(); + refreshRecentGrid(); refreshCommonGrid(); }, onStartTrackingTouch: function() {}, @@ -4432,15 +4581,20 @@ FloatBallAppWM.prototype.showColorPickerPopup = function(opts) { })(ri); } - // 初始化 RGB 滑块值 - try { - var initR = parseInt(currentBaseRgbHex.slice(0, 2), 16) || 0; - var initG = parseInt(currentBaseRgbHex.slice(2, 4), 16) || 0; - var initB = parseInt(currentBaseRgbHex.slice(4, 6), 16) || 0; - rgbSeeks[0].setProgress(initR); - rgbSeeks[1].setProgress(initG); - rgbSeeks[2].setProgress(initB); - } catch(e) {} + function syncRgbSeeks() { + try { + var initR = parseInt(currentBaseRgbHex.slice(0, 2), 16) || 0; + var initG = parseInt(currentBaseRgbHex.slice(2, 4), 16) || 0; + var initB = parseInt(currentBaseRgbHex.slice(4, 6), 16) || 0; + rgbSeeks[0].setProgress(initR); + rgbSeeks[1].setProgress(initG); + rgbSeeks[2].setProgress(initB); + rgbValTvs[0].setText(String(initR)); + rgbValTvs[1].setText(String(initG)); + rgbValTvs[2].setText(String(initB)); + } catch(e) {} + } + syncRgbSeeks(); // 透明度滑块 var alphaRow = new android.widget.LinearLayout(context); @@ -4477,6 +4631,7 @@ FloatBallAppWM.prototype.showColorPickerPopup = function(opts) { selectedColor = buildArgbHex(currentAlphaByte, currentBaseRgbHex); updatePreview(); updateValueTv(); + refreshRecentGrid(); refreshCommonGrid(); }, onStartTrackingTouch: function() {}, @@ -4499,13 +4654,21 @@ FloatBallAppWM.prototype.showColorPickerPopup = function(opts) { selectedColor = ""; updatePreview(); updateValueTv(); + refreshRecentGrid(); refreshCommonGrid(); + syncRgbSeeks(); + alphaSeek.setProgress(255); + alphaValTv.setText("255"); + currentAlphaByte = 255; }); actionRow.addView(btnClear); var btnOk = self.ui.createSolidButton(self, "确定", C.primary, android.graphics.Color.WHITE, function() { self.touchActivity(); var finalColor = isFollowTheme ? "" : selectedColor; + if (!isFollowTheme && selectedColor) { + pushRecentColor(selectedColor); + } if (typeof onSelect === "function") onSelect(finalColor); closePopup(); });