Migrate sticky windows back to QWidget

This commit is contained in:
zhatong committed 2026-03-26 20:09:30 +08:00
1 parent 965d05634a
commit 79658afee2
51 files changed
+2552 -4777

No files matched your search

+6
View File
@@ -56,6 +56,10 @@ set(SOURCES
src/instance_activation.cpp
src/instance_activation.h
src/singleton.h
src/widgets/sticky_pin_widget.cpp
src/widgets/sticky_pin_widget.h
src/widgets/sticky_canvas_widget.cpp
src/widgets/sticky_canvas_widget.h
# Model
src/model/desktop_snapshot.cpp
@@ -104,6 +108,8 @@ set(SOURCES
src/paint_board/shape/two_point_shape.h
src/paint_board/shape/arrow_shape.cpp
src/paint_board/shape/arrow_shape.h
src/paint_board/shape/blur_shape.cpp
src/paint_board/shape/blur_shape.h
src/paint_board/shape/ellipse_shape.cpp
src/paint_board/shape/ellipse_shape.h
src/paint_board/shape/mosaic_shape.cpp
+99 -99
View File
@@ -93,37 +93,37 @@
<context>
<name>TZChat</name>
<message>
<location filename="../qml/TZChat.qml" line="83" />
<location filename="../qml/Windows/TZChat.qml" line="83" />
<source>AI 图像编辑</source>
<translation>AI Image Edit</translation>
</message>
<message>
<location filename="../qml/TZChat.qml" line="162" />
<location filename="../qml/Windows/TZChat.qml" line="162" />
<source>当前贴图</source>
<translation>Current Image</translation>
</message>
<message>
<location filename="../qml/TZChat.qml" line="171" />
<location filename="../qml/Windows/TZChat.qml" line="171" />
<source>未关联贴图</source>
<translation>No Image Linked</translation>
</message>
<message>
<location filename="../qml/TZChat.qml" line="178" />
<location filename="../qml/Windows/TZChat.qml" line="178" />
<source>发送后将对此图执行 AI 编辑</source>
<translation>AI edit will be applied to this image after sending</translation>
</message>
<message>
<location filename="../qml/TZChat.qml" line="203" />
<location filename="../qml/Windows/TZChat.qml" line="203" />
<source>描述您想要的修改...</source>
<translation>Describe the changes you want...</translation>
</message>
<message>
<location filename="../qml/TZChat.qml" line="228" />
<location filename="../qml/Windows/TZChat.qml" line="228" />
<source>取消</source>
<translation>Cancel</translation>
</message>
<message>
<location filename="../qml/TZChat.qml" line="245" />
<location filename="../qml/Windows/TZChat.qml" line="245" />
<source>发送</source>
<translation>Send</translation>
</message>
@@ -189,7 +189,7 @@
<context>
<name>TZGifRecordBar</name>
<message>
<location filename="../qml/TZGifRecordBar.qml" line="186" />
<location filename="../qml/Capture/TZGifRecordBar.qml" line="186" />
<source>正在生成 GIF...</source>
<translation type="unfinished" />
</message>
@@ -266,52 +266,52 @@ If it conflicts with system or other app shortcuts, change to a different combin
<context>
<name>TZMarkToolInfo</name>
<message>
<location filename="../qml/TZMarkToolInfo.qml" line="13" />
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="13" />
<source>文本</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../qml/TZMarkToolInfo.qml" line="62" />
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="62" />
<source>细</source>
<translation>Thin</translation>
</message>
<message>
<location filename="../qml/TZMarkToolInfo.qml" line="63" />
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="63" />
<source>中</source>
<translation>Medium</translation>
</message>
<message>
<location filename="../qml/TZMarkToolInfo.qml" line="64" />
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="64" />
<source>粗</source>
<translation>Thick</translation>
</message>
<message>
<location filename="../qml/TZMarkToolInfo.qml" line="65" />
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="65" />
<source>特粗</source>
<translation>Extra Thick</translation>
</message>
<message>
<location filename="../qml/TZMarkToolInfo.qml" line="133" />
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="133" />
<source>输入文字</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../qml/TZMarkToolInfo.qml" line="140" />
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="140" />
<source>背景</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../qml/TZMarkToolInfo.qml" line="152" />
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="152" />
<source>模糊</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../qml/TZMarkToolInfo.qml" line="177" />
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="177" />
<source>自动递增</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../qml/TZMarkToolInfo.qml" line="187" />
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="187" />
<source>数字</source>
<translation type="unfinished" />
</message>
@@ -319,32 +319,32 @@ If it conflicts with system or other app shortcuts, change to a different combin
<context>
<name>TZOcrResult</name>
<message>
<location filename="../qml/TZOcrResult.qml" line="67" />
<location filename="../qml/Windows/TZOcrResult.qml" line="67" />
<source>OCR 识别结果</source>
<translation>OCR Results</translation>
</message>
<message>
<location filename="../qml/TZOcrResult.qml" line="167" />
<location filename="../qml/Windows/TZOcrResult.qml" line="167" />
<source>正在识别中…</source>
<translation>Recognizing…</translation>
</message>
<message>
<location filename="../qml/TZOcrResult.qml" line="190" />
<location filename="../qml/Windows/TZOcrResult.qml" line="190" />
<source>识别结果将显示在这里…</source>
<translation>Recognition results will appear here…</translation>
</message>
<message>
<location filename="../qml/TZOcrResult.qml" line="239" />
<location filename="../qml/Windows/TZOcrResult.qml" line="239" />
<source>复制全部</source>
<translation>Copy All</translation>
</message>
<message>
<location filename="../qml/TZOcrResult.qml" line="265" />
<location filename="../qml/Windows/TZOcrResult.qml" line="265" />
<source>关闭</source>
<translation>Close</translation>
</message>
<message>
<location filename="../qml/TZOcrResult.qml" line="293" />
<location filename="../qml/Windows/TZOcrResult.qml" line="293" />
<source>已复制!</source>
<translation>Copied!</translation>
</message>
@@ -378,46 +378,46 @@ If it conflicts with system or other app shortcuts, change to a different combin
<context>
<name>TZStickyWindow</name>
<message>
<location filename="../qml/TZStickyWindow.qml" line="515" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="515" />
<source>当前透明度 %1%</source>
<translation>Opacity: %1%</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="518" />
<location filename="../qml/TZStickyWindow.qml" line="572" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="518" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="572" />
<source>覆盖贴图(合并标注)</source>
<translation>Overwrite Image (Merge Annotations)</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="523" />
<location filename="../qml/TZStickyWindow.qml" line="558" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="523" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="558" />
<source>显示工具栏</source>
<translation>Show Toolbar</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="534" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="534" />
<source>透明度 +5%</source>
<translation>Opacity +5%</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="535" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="535" />
<source>透明度 -5%</source>
<translation>Opacity -5%</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="541" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="541" />
<source>旋转 90°</source>
<translation>Rotate 90°</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="548" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="548" />
<source>重置位置</source>
<translation>Reset Position</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="520" />
<location filename="../qml/TZStickyWindow.qml" line="566" />
<location filename="../qml/TZStickyWindow.qml" line="581" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="520" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="566" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="581" />
<source>AI 编辑</source>
<translation>AI Edit</translation>
</message>
@@ -426,120 +426,120 @@ If it conflicts with system or other app shortcuts, change to a different combin
<translation type="vanished">Save Image</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="519" />
<location filename="../qml/TZStickyWindow.qml" line="573" />
<location filename="../qml/TZStickyWindow.qml" line="580" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="519" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="573" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="580" />
<source>复制贴图</source>
<translation>Copy Image</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="530" />
<location filename="../qml/TZStickyWindow.qml" line="583" />
<location filename="../qml/TZStickyWindow.qml" line="588" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="530" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="583" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="588" />
<source>关闭窗口</source>
<translation>Close Window</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="570" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="570" />
<source>保存原图</source>
<translation>Save Original Image</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="571" />
<location filename="../qml/TZStickyWindow.qml" line="602" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="571" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="602" />
<source>另存为(合并标注)</source>
<translation>Save As (Merge Annotations)</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="578" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="578" />
<source>常用项置顶</source>
<translation>Pin Common Items</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="579" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="579" />
<source>覆盖贴图</source>
<translation>Overwrite Image</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="582" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="582" />
<source>显示/隐藏工具栏</source>
<translation>Show/Hide Toolbar</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="593" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="593" />
<source>选择保存位置</source>
<translation>Choose Save Location</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="341" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="341" />
<source>Pencil</source>
<translation>Pencil</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="350" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="350" />
<source>Rectangle</source>
<translation>Rectangle</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="359" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="359" />
<source>Circle</source>
<translation>Circle</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="368" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="368" />
<source>Arrow</source>
<translation>Arrow</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="382" />
<location filename="../qml/TZStickyWindow.qml" line="383" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="382" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="383" />
<source>Mosaic</source>
<translation>Mosaic</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="404" />
<location filename="../qml/TZStickyWindow.qml" line="539" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="404" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="539" />
<source>缩小</source>
<translation>Zoom Out</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="406" />
<location filename="../qml/TZStickyWindow.qml" line="540" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="406" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="540" />
<source>放大</source>
<translation>Zoom In</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="407" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="407" />
<source>旋转90°</source>
<translation>Rotate 90°</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="408" />
<location filename="../qml/TZStickyWindow.qml" line="542" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="408" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="542" />
<source>镜像</source>
<translation>Mirror</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="409" />
<location filename="../qml/TZStickyWindow.qml" line="543" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="409" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="543" />
<source>恢复原始大小</source>
<translation>Reset Size</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="415" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="415" />
<source>完成并复制</source>
<translation>Apply and Copy</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="422" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="422" />
<source>撤销</source>
<translation>Undo</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="434" />
<location filename="../qml/TZStickyWindow.qml" line="435" />
<location filename="../qml/TZStickyWindow.qml" line="523" />
<location filename="../qml/TZStickyWindow.qml" line="558" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="434" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="435" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="523" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="558" />
<source>隐藏工具栏</source>
<translation>Hide Toolbar</translation>
</message>
@@ -560,8 +560,8 @@ If it conflicts with system or other app shortcuts, change to a different combin
<translation>Hide Toolbar</translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="324" />
<location filename="../qml/TZStickyWindow.qml" line="325" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="324" />
<location filename="../qml/Windows/TZStickyWindow.qml" line="325" />
<source>OCR</source>
<translation>OCR</translation>
</message>
@@ -573,7 +573,7 @@ If it conflicts with system or other app shortcuts, change to a different combin
<translation type="vanished">Confirm</translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="83" />
<location filename="../qml/Capture/TZTool.qml" line="83" />
<source>取消</source>
<translation>Cancel</translation>
</message>
@@ -598,67 +598,67 @@ If it conflicts with system or other app shortcuts, change to a different combin
<translation type="vanished">Arrow</translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="76" />
<location filename="../qml/Capture/TZTool.qml" line="76" />
<source>完成</source>
<translation>Done</translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="100" />
<location filename="../qml/Capture/TZTool.qml" line="100" />
<source>录制GIF</source>
<translation>Record GIF</translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="109" />
<location filename="../qml/Capture/TZTool.qml" line="109" />
<source>保存</source>
<translation>Save</translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="116" />
<location filename="../qml/Capture/TZTool.qml" line="116" />
<source>贴图</source>
<translation>Pin</translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="127" />
<location filename="../qml/Capture/TZTool.qml" line="127" />
<source>矩形</source>
<translation>Rectangle</translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="136" />
<location filename="../qml/Capture/TZTool.qml" line="136" />
<source>圆形</source>
<translation>Circle</translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="145" />
<location filename="../qml/Capture/TZTool.qml" line="145" />
<source>箭头</source>
<translation>Arrow</translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="154" />
<location filename="../qml/Capture/TZTool.qml" line="154" />
<source>画笔</source>
<translation>Pen</translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="163" />
<location filename="../qml/Capture/TZTool.qml" line="163" />
<source>高亮</source>
<translation>Highlight</translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="172" />
<location filename="../qml/Capture/TZTool.qml" line="172" />
<source>马赛克</source>
<translation>Mosaic</translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="181" />
<location filename="../qml/Capture/TZTool.qml" line="181" />
<source>文字</source>
<translation>Text</translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="190" />
<location filename="../qml/Capture/TZTool.qml" line="190" />
<source>序号</source>
<translation>Number</translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="197" />
<location filename="../qml/Capture/TZTool.qml" line="197" />
<source>撤销</source>
<translation>Undo</translation>
</message>
@@ -667,7 +667,7 @@ If it conflicts with system or other app shortcuts, change to a different combin
<translation>Number</translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="92" />
<location filename="../qml/Capture/TZTool.qml" line="92" />
<source>OCR</source>
<translation>OCR</translation>
</message>
@@ -703,7 +703,7 @@ If it conflicts with system or other app shortcuts, change to a different combin
<translation type="vanished">Failed to save screenshot file</translation>
</message>
<message>
<location filename="../qml/TZWindow.qml" line="563" />
<location filename="../qml/Capture/TZWindow.qml" line="563" />
<source>%1 * %2</source>
<translation>%1 * %2</translation>
</message>
@@ -724,57 +724,57 @@ If it conflicts with system or other app shortcuts, change to a different combin
<translation type="vanished">Failed to copy screenshot to clipboard</translation>
</message>
<message>
<location filename="../qml/TZWindow.qml" line="134" />
<location filename="../qml/Capture/TZWindow.qml" line="134" />
<source>Capture done</source>
<translation>Capture done</translation>
</message>
<message>
<location filename="../qml/TZWindow.qml" line="134" />
<location filename="../qml/Capture/TZWindow.qml" line="134" />
<source>Copied to clipboard</source>
<translation>Copied to clipboard</translation>
</message>
<message>
<location filename="../qml/TZWindow.qml" line="136" />
<location filename="../qml/Capture/TZWindow.qml" line="136" />
<source>Capture failed</source>
<translation>Capture failed</translation>
</message>
<message>
<location filename="../qml/TZWindow.qml" line="136" />
<location filename="../qml/Capture/TZWindow.qml" line="136" />
<source>Failed to copy image</source>
<translation>Failed to copy image</translation>
</message>
<message>
<location filename="../qml/TZWindow.qml" line="434" />
<location filename="../qml/Capture/TZWindow.qml" line="434" />
<source>Select save location</source>
<translation>Select save location</translation>
</message>
<message>
<location filename="../qml/TZWindow.qml" line="442" />
<location filename="../qml/Capture/TZWindow.qml" line="442" />
<source>Save success</source>
<translation>Save success</translation>
</message>
<message>
<location filename="../qml/TZWindow.qml" line="442" />
<location filename="../qml/Capture/TZWindow.qml" line="442" />
<source>Screenshot saved to file</source>
<translation>Screenshot saved to file</translation>
</message>
<message>
<location filename="../qml/TZWindow.qml" line="445" />
<location filename="../qml/Capture/TZWindow.qml" line="445" />
<source>Save failed</source>
<translation>Save failed</translation>
</message>
<message>
<location filename="../qml/TZWindow.qml" line="445" />
<location filename="../qml/Capture/TZWindow.qml" line="445" />
<source>Failed to save screenshot file</source>
<translation>Failed to save screenshot file</translation>
</message>
<message>
<location filename="../qml/TZWindow.qml" line="705" />
<location filename="../qml/Capture/TZWindow.qml" line="705" />
<source>Cancel</source>
<translation>Cancel</translation>
</message>
<message>
<location filename="../qml/TZWindow.qml" line="727" />
<location filename="../qml/Capture/TZWindow.qml" line="727" />
<source>OK</source>
<translation>OK</translation>
</message>
+99 -99
View File
@@ -82,37 +82,37 @@
<context>
<name>TZChat</name>
<message>
<location filename="../qml/TZChat.qml" line="83"/>
<location filename="../qml/Windows/TZChat.qml" line="83"/>
<source>AI 图像编辑</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZChat.qml" line="162"/>
<location filename="../qml/Windows/TZChat.qml" line="162"/>
<source>当前贴图</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZChat.qml" line="171"/>
<location filename="../qml/Windows/TZChat.qml" line="171"/>
<source>未关联贴图</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZChat.qml" line="178"/>
<location filename="../qml/Windows/TZChat.qml" line="178"/>
<source>发送后将对此图执行 AI 编辑</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZChat.qml" line="203"/>
<location filename="../qml/Windows/TZChat.qml" line="203"/>
<source>描述您想要的修改...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZChat.qml" line="228"/>
<location filename="../qml/Windows/TZChat.qml" line="228"/>
<source>取消</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZChat.qml" line="245"/>
<location filename="../qml/Windows/TZChat.qml" line="245"/>
<source>发送</source>
<translation type="unfinished"></translation>
</message>
@@ -178,7 +178,7 @@
<context>
<name>TZGifRecordBar</name>
<message>
<location filename="../qml/TZGifRecordBar.qml" line="186"/>
<location filename="../qml/Capture/TZGifRecordBar.qml" line="186"/>
<source>正在生成 GIF...</source>
<translation type="unfinished"></translation>
</message>
@@ -254,52 +254,52 @@
<context>
<name>TZMarkToolInfo</name>
<message>
<location filename="../qml/TZMarkToolInfo.qml" line="13"/>
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="13"/>
<source>文本</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZMarkToolInfo.qml" line="62"/>
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="62"/>
<source>细</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZMarkToolInfo.qml" line="63"/>
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="63"/>
<source>中</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZMarkToolInfo.qml" line="64"/>
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="64"/>
<source>粗</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZMarkToolInfo.qml" line="65"/>
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="65"/>
<source>特粗</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZMarkToolInfo.qml" line="133"/>
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="133"/>
<source>输入文字</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZMarkToolInfo.qml" line="140"/>
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="140"/>
<source>背景</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZMarkToolInfo.qml" line="152"/>
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="152"/>
<source>模糊</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZMarkToolInfo.qml" line="177"/>
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="177"/>
<source>自动递增</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZMarkToolInfo.qml" line="187"/>
<location filename="../qml/Capture/TZMarkToolInfo.qml" line="187"/>
<source>数字</source>
<translation type="unfinished"></translation>
</message>
@@ -307,32 +307,32 @@
<context>
<name>TZOcrResult</name>
<message>
<location filename="../qml/TZOcrResult.qml" line="67"/>
<location filename="../qml/Windows/TZOcrResult.qml" line="67"/>
<source>OCR 识别结果</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZOcrResult.qml" line="167"/>
<location filename="../qml/Windows/TZOcrResult.qml" line="167"/>
<source>正在识别中…</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZOcrResult.qml" line="190"/>
<location filename="../qml/Windows/TZOcrResult.qml" line="190"/>
<source>识别结果将显示在这里…</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZOcrResult.qml" line="239"/>
<location filename="../qml/Windows/TZOcrResult.qml" line="239"/>
<source>复制全部</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZOcrResult.qml" line="265"/>
<location filename="../qml/Windows/TZOcrResult.qml" line="265"/>
<source>关闭</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZOcrResult.qml" line="293"/>
<location filename="../qml/Windows/TZOcrResult.qml" line="293"/>
<source>已复制!</source>
<translation type="unfinished"></translation>
</message>
@@ -366,170 +366,170 @@
<context>
<name>TZStickyWindow</name>
<message>
<location filename="../qml/TZStickyWindow.qml" line="324"/>
<location filename="../qml/TZStickyWindow.qml" line="325"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="324"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="325"/>
<source>OCR</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="341"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="341"/>
<source>Pencil</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="350"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="350"/>
<source>Rectangle</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="359"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="359"/>
<source>Circle</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="368"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="368"/>
<source>Arrow</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="382"/>
<location filename="../qml/TZStickyWindow.qml" line="383"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="382"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="383"/>
<source>Mosaic</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="404"/>
<location filename="../qml/TZStickyWindow.qml" line="539"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="404"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="539"/>
<source>缩小</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="406"/>
<location filename="../qml/TZStickyWindow.qml" line="540"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="406"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="540"/>
<source>放大</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="407"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="407"/>
<source>旋转90°</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="408"/>
<location filename="../qml/TZStickyWindow.qml" line="542"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="408"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="542"/>
<source>镜像</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="409"/>
<location filename="../qml/TZStickyWindow.qml" line="543"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="409"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="543"/>
<source>恢复原始大小</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="415"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="415"/>
<source>完成并复制</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="422"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="422"/>
<source>撤销</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="434"/>
<location filename="../qml/TZStickyWindow.qml" line="435"/>
<location filename="../qml/TZStickyWindow.qml" line="523"/>
<location filename="../qml/TZStickyWindow.qml" line="558"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="434"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="435"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="523"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="558"/>
<source>隐藏工具栏</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="515"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="515"/>
<source>当前透明度 %1%</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="518"/>
<location filename="../qml/TZStickyWindow.qml" line="572"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="518"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="572"/>
<source>覆盖贴图(合并标注)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="519"/>
<location filename="../qml/TZStickyWindow.qml" line="573"/>
<location filename="../qml/TZStickyWindow.qml" line="580"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="519"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="573"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="580"/>
<source>复制贴图</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="520"/>
<location filename="../qml/TZStickyWindow.qml" line="566"/>
<location filename="../qml/TZStickyWindow.qml" line="581"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="520"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="566"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="581"/>
<source>AI 编辑</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="523"/>
<location filename="../qml/TZStickyWindow.qml" line="558"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="523"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="558"/>
<source>显示工具栏</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="530"/>
<location filename="../qml/TZStickyWindow.qml" line="583"/>
<location filename="../qml/TZStickyWindow.qml" line="588"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="530"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="583"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="588"/>
<source>关闭窗口</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="534"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="534"/>
<source>透明度 +5%</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="535"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="535"/>
<source>透明度 -5%</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="541"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="541"/>
<source>旋转 90°</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="548"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="548"/>
<source>重置位置</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="570"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="570"/>
<source>保存原图</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="571"/>
<location filename="../qml/TZStickyWindow.qml" line="602"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="571"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="602"/>
<source>另存为(合并标注)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="578"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="578"/>
<source>常用项置顶</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="579"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="579"/>
<source>覆盖贴图</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="582"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="582"/>
<source>显示/隐藏工具栏</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZStickyWindow.qml" line="593"/>
<location filename="../qml/Windows/TZStickyWindow.qml" line="593"/>
<source>选择保存位置</source>
<translation type="unfinished"></translation>
</message>
@@ -537,77 +537,77 @@
<context>
<name>TZTool</name>
<message>
<location filename="../qml/TZTool.qml" line="83"/>
<location filename="../qml/Capture/TZTool.qml" line="83"/>
<source>取消</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="76"/>
<location filename="../qml/Capture/TZTool.qml" line="76"/>
<source>完成</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="92"/>
<location filename="../qml/Capture/TZTool.qml" line="92"/>
<source>OCR</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="100"/>
<location filename="../qml/Capture/TZTool.qml" line="100"/>
<source>录制GIF</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="109"/>
<location filename="../qml/Capture/TZTool.qml" line="109"/>
<source>保存</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="116"/>
<location filename="../qml/Capture/TZTool.qml" line="116"/>
<source>贴图</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="127"/>
<location filename="../qml/Capture/TZTool.qml" line="127"/>
<source>矩形</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="136"/>
<location filename="../qml/Capture/TZTool.qml" line="136"/>
<source>圆形</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="145"/>
<location filename="../qml/Capture/TZTool.qml" line="145"/>
<source>箭头</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="154"/>
<location filename="../qml/Capture/TZTool.qml" line="154"/>
<source>画笔</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="163"/>
<location filename="../qml/Capture/TZTool.qml" line="163"/>
<source>高亮</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="172"/>
<location filename="../qml/Capture/TZTool.qml" line="172"/>
<source>马赛克</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="181"/>
<location filename="../qml/Capture/TZTool.qml" line="181"/>
<source>文字</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="190"/>
<location filename="../qml/Capture/TZTool.qml" line="190"/>
<source>序号</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZTool.qml" line="197"/>
<location filename="../qml/Capture/TZTool.qml" line="197"/>
<source>撤销</source>
<translation type="unfinished"></translation>
</message>
@@ -615,62 +615,62 @@
<context>
<name>TZWindow</name>
<message>
<location filename="../qml/TZWindow.qml" line="134"/>
<location filename="../qml/Capture/TZWindow.qml" line="134"/>
<source>Capture done</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZWindow.qml" line="134"/>
<location filename="../qml/Capture/TZWindow.qml" line="134"/>
<source>Copied to clipboard</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZWindow.qml" line="136"/>
<location filename="../qml/Capture/TZWindow.qml" line="136"/>
<source>Capture failed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZWindow.qml" line="136"/>
<location filename="../qml/Capture/TZWindow.qml" line="136"/>
<source>Failed to copy image</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZWindow.qml" line="434"/>
<location filename="../qml/Capture/TZWindow.qml" line="434"/>
<source>Select save location</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZWindow.qml" line="442"/>
<location filename="../qml/Capture/TZWindow.qml" line="442"/>
<source>Save success</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZWindow.qml" line="442"/>
<location filename="../qml/Capture/TZWindow.qml" line="442"/>
<source>Screenshot saved to file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZWindow.qml" line="445"/>
<location filename="../qml/Capture/TZWindow.qml" line="445"/>
<source>Save failed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZWindow.qml" line="445"/>
<location filename="../qml/Capture/TZWindow.qml" line="445"/>
<source>Failed to save screenshot file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZWindow.qml" line="563"/>
<location filename="../qml/Capture/TZWindow.qml" line="563"/>
<source>%1 * %2</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZWindow.qml" line="705"/>
<location filename="../qml/Capture/TZWindow.qml" line="705"/>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/TZWindow.qml" line="727"/>
<location filename="../qml/Capture/TZWindow.qml" line="727"/>
<source>OK</source>
<translation type="unfinished"></translation>
</message>
+2 -5
View File
@@ -19,16 +19,13 @@
<file>qml/Capture/TZMagnifyingGlass.qml</file>
<file>qml/Capture/TZGifRecordBar.qml</file>
<file>qml/Windows/TZStickyWindow.qml</file>
<file>qml/Windows/TZStickyExtraToolBar.qml</file>
<file>qml/Windows/TZOcrResult.qml</file>
<file>qml/Windows/TZChat.qml</file>
<file>qml/Windows/TZLoadingSpinner.qml</file>
<file>qml/Setting/TZSettingWin.qml</file>
<file>qml/Setting/TZGeneralSettings.qml</file>
<file>qml/Setting/TZRightPanel.qml</file>
<file>qml/TZControls/qmldir</file>
<file>qml/TZControls/TZComboBox.qml</file>
<file>qml/TZStickyContextMenu.qml</file>
<file>qml/Setting/TZHotkeySettings.qml</file>
<file>qml/Setting/TZAboutWin.qml</file>
<file>resource/img/lc_check.svg</file>
@@ -39,6 +36,7 @@
<file>resource/img/lc_pencil.svg</file>
<file>resource/img/lc_circle.svg</file>
<file>resource/img/lc_arrow.svg</file>
<file>resource/img/lc_blur.svg</file>
<file>resource/img/lc_mosaic.svg</file>
<file>resource/img/lc_text.svg</file>
<file>resource/img/lc_list_ordered.svg</file>
@@ -47,8 +45,6 @@
<file>resource/img/tray_icon.svg</file>
<file>resource/img/lc_close.svg</file>
<file>resource/img/lc_chat.svg</file>
<file>resource/img/lc_emoji.svg</file>
<file>resource/img/lc_file.svg</file>
<file>resource/img/lc_send.svg</file>
<file>qml/LongCapture/TZLongCaptureBar.qml</file>
<file>qml/LongCapture/TZLongCapturePreview.qml</file>
@@ -57,5 +53,6 @@
<file>resource/img/lc_gif_stop.svg</file>
<file>resource/img/lc_ocr.svg</file>
<file>resource/img/lc_longshot.svg</file>
<file>resource/qss/sticky_pin_widget.qss</file>
</qresource>
</RCC>
+59 -46
View File
@@ -1,17 +1,14 @@
import QtQuick 2.12
import QtQuick 2.12
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.5
Rectangle {
id: tool
radius: 10
color: "#FFFFFF"
visible: false
border.color: "#E2E8F0"
border.width: 1
layer.enabled: true
implicitWidth: toolRow.implicitWidth + 12
implicitHeight: 44
signal signalScreenshot
signal signalCancel
@@ -42,6 +39,18 @@ Rectangle {
property bool showMosaicButton: true
property bool showTextButton: true
property bool showNumberButton: true
readonly property int buttonSize: 32
readonly property int buttonRadius: 7
readonly property int iconSize: 18
readonly property int dividerHeight: 20
readonly property int outerRadius: 10
readonly property int outerMargin: 6
readonly property int bubbleRadius: 6
readonly property int bubbleHeight: 24
readonly property int bubblePadding: 14
readonly property int bubbleGap: 6
readonly property int tipFontSize: 11
readonly property int rowSpacing: 2
readonly property int shapeType: {
switch (activeTool) {
@@ -82,161 +91,165 @@ Rectangle {
hoverTipText = ""
}
radius: outerRadius
implicitWidth: toolRow.implicitWidth + outerMargin * 2
implicitHeight: Math.max(buttonSize + outerMargin * 2, 44)
RowLayout {
id: toolRow
anchors.fill: parent
anchors.margins: 6
anchors.margins: tool.outerMargin
layoutDirection: Qt.RightToLeft
spacing: 2
spacing: tool.rowSpacing
Rectangle {
visible: tool.showScreenshotButton
width: 32; height: 32; radius: 7
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
color: checkArea.containsMouse ? "#DCFCE7" : "transparent"
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_check.svg"; width: 18; height: 18 }
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_check.svg"; width: tool.iconSize; height: tool.iconSize }
MouseArea { id: checkArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("完成"), parent); onExited: tool.hideTip(qsTr("完成")); onPressed: signalScreenshot() }
}
Rectangle {
visible: tool.showCancelButton
width: 32; height: 32; radius: 7
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
color: closeArea.containsMouse ? "#FEE2E2" : "transparent"
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_x.svg"; width: 18; height: 18 }
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_x.svg"; width: tool.iconSize; height: tool.iconSize }
MouseArea { id: closeArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("取消"), parent); onExited: tool.hideTip(qsTr("取消")); onPressed: signalCancel() }
}
Rectangle { visible: tool.showScreenshotButton || tool.showCancelButton; width: 1; height: 20; color: "#E2E8F0"; Layout.alignment: Qt.AlignVCenter }
Rectangle { visible: tool.showScreenshotButton || tool.showCancelButton; width: 1; height: tool.dividerHeight; color: "#E2E8F0"; Layout.alignment: Qt.AlignVCenter }
Rectangle {
visible: tool.showOcrButton
width: 32; height: 32; radius: 7
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
color: ocrArea.containsMouse ? "#F0FDF4" : "transparent"
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_ocr.svg"; width: 18; height: 18 }
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_ocr.svg"; width: tool.iconSize; height: tool.iconSize }
MouseArea { id: ocrArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("OCR"), parent); onExited: tool.hideTip(qsTr("OCR")); onPressed: signalOcr() }
}
Rectangle {
id: gifRecordBtn
visible: tool.showGifRecordButton
width: 32; height: 32; radius: 7
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
color: gifArea.containsMouse ? "#FFF7ED" : "transparent"
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_gif_record.svg"; width: 18; height: 18 }
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_gif_record.svg"; width: tool.iconSize; height: tool.iconSize }
MouseArea { id: gifArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("录制GIF"), parent); onExited: tool.hideTip(qsTr("录制GIF")); onPressed: signalGifRecord() }
}
Rectangle {
visible: tool.showLongScreenshotButton
width: 32; height: 32; radius: 7
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
color: longShotArea.containsMouse ? "#FFF7ED" : "transparent"
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_longshot.svg"; width: 18; height: 18 }
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_longshot.svg"; width: tool.iconSize; height: tool.iconSize }
MouseArea { id: longShotArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("长截图"), parent); onExited: tool.hideTip(qsTr("长截图")); onPressed: signalLongScreenshot() }
}
Rectangle { visible: tool.showOcrButton || tool.showGifRecordButton || tool.showLongScreenshotButton; width: 1; height: 20; color: "#E2E8F0"; Layout.alignment: Qt.AlignVCenter }
Rectangle { visible: tool.showOcrButton || tool.showGifRecordButton || tool.showLongScreenshotButton; width: 1; height: tool.dividerHeight; color: "#E2E8F0"; Layout.alignment: Qt.AlignVCenter }
Rectangle {
visible: tool.showSaveButton
width: 32; height: 32; radius: 7
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
color: saveArea.containsMouse ? "#EFF6FF" : "transparent"
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_save.svg"; width: 18; height: 18 }
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_save.svg"; width: tool.iconSize; height: tool.iconSize }
MouseArea { id: saveArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("保存"), parent); onExited: tool.hideTip(qsTr("保存")); onPressed: signalSave() }
}
Rectangle {
visible: tool.showStickyButton
width: 32; height: 32; radius: 7
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
color: fixArea.containsMouse ? "#EFF6FF" : "transparent"
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_pin.svg"; width: 18; height: 18 }
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_pin.svg"; width: tool.iconSize; height: tool.iconSize }
MouseArea { id: fixArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("贴图"), parent); onExited: tool.hideTip(qsTr("贴图")); onPressed: signalSticky() }
}
Rectangle { visible: tool.showSaveButton || tool.showStickyButton; width: 1; height: 20; color: "#E2E8F0"; Layout.alignment: Qt.AlignVCenter }
Rectangle { visible: tool.showSaveButton || tool.showStickyButton; width: 1; height: tool.dividerHeight; color: "#E2E8F0"; Layout.alignment: Qt.AlignVCenter }
Rectangle {
visible: tool.showRectButton
width: 32; height: 32; radius: 7
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
color: (tool.activeTool === "rect") ? "#EFF6FF" : (rectArea.containsMouse ? "#F1F5F9" : "transparent")
border.color: (tool.activeTool === "rect") ? "#3B82F6" : "transparent"
border.width: 1
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_square.svg"; width: 18; height: 18 }
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_square.svg"; width: tool.iconSize; height: tool.iconSize }
MouseArea { id: rectArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("矩形"), parent); onExited: tool.hideTip(qsTr("矩形")); onPressed: tool.setActiveTool("rect") }
}
Rectangle {
visible: tool.showCircleButton
width: 32; height: 32; radius: 7
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
color: (tool.activeTool === "circle") ? "#EFF6FF" : (circleArea.containsMouse ? "#F1F5F9" : "transparent")
border.color: (tool.activeTool === "circle") ? "#3B82F6" : "transparent"
border.width: 1
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_circle.svg"; width: 18; height: 18 }
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_circle.svg"; width: tool.iconSize; height: tool.iconSize }
MouseArea { id: circleArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("圆形"), parent); onExited: tool.hideTip(qsTr("圆形")); onPressed: tool.setActiveTool("circle") }
}
Rectangle {
visible: tool.showArrowButton
width: 32; height: 32; radius: 7
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
color: (tool.activeTool === "arrow") ? "#EFF6FF" : (arrowArea.containsMouse ? "#F1F5F9" : "transparent")
border.color: (tool.activeTool === "arrow") ? "#3B82F6" : "transparent"
border.width: 1
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_arrow.svg"; width: 18; height: 18 }
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_arrow.svg"; width: tool.iconSize; height: tool.iconSize }
MouseArea { id: arrowArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("箭头"), parent); onExited: tool.hideTip(qsTr("箭头")); onPressed: tool.setActiveTool("arrow") }
}
Rectangle {
visible: tool.showPencilButton
width: 32; height: 32; radius: 7
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
color: (tool.activeTool === "pencil") ? "#EFF6FF" : (pencilArea.containsMouse ? "#F1F5F9" : "transparent")
border.color: (tool.activeTool === "pencil") ? "#3B82F6" : "transparent"
border.width: 1
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_pencil.svg"; width: 18; height: 18 }
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_pencil.svg"; width: tool.iconSize; height: tool.iconSize }
MouseArea { id: pencilArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("画笔"), parent); onExited: tool.hideTip(qsTr("画笔")); onPressed: tool.setActiveTool("pencil") }
}
Rectangle {
visible: tool.showHighlightButton
width: 32; height: 32; radius: 7
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
color: (tool.activeTool === "highlight") ? "#EFF6FF" : (highlightArea.containsMouse ? "#F1F5F9" : "transparent")
border.color: (tool.activeTool === "highlight") ? "#3B82F6" : "transparent"
border.width: 1
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_highlighter.svg"; width: 18; height: 18 }
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_highlighter.svg"; width: tool.iconSize; height: tool.iconSize }
MouseArea { id: highlightArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("高亮"), parent); onExited: tool.hideTip(qsTr("高亮")); onPressed: tool.setActiveTool("highlight") }
}
Rectangle {
visible: tool.showMosaicButton
width: 32; height: 32; radius: 7
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
color: (tool.activeTool === "mosaic") ? "#EFF6FF" : (mosaicArea.containsMouse ? "#F1F5F9" : "transparent")
border.color: (tool.activeTool === "mosaic") ? "#3B82F6" : "transparent"
border.width: 1
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_mosaic.svg"; width: 18; height: 18 }
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_mosaic.svg"; width: tool.iconSize; height: tool.iconSize }
MouseArea { id: mosaicArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("马赛克"), parent); onExited: tool.hideTip(qsTr("马赛克")); onPressed: tool.setActiveTool("mosaic") }
}
Rectangle {
visible: tool.showTextButton
width: 32; height: 32; radius: 7
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
color: (tool.activeTool === "text") ? "#EFF6FF" : (textArea.containsMouse ? "#F1F5F9" : "transparent")
border.color: (tool.activeTool === "text") ? "#3B82F6" : "transparent"
border.width: 1
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_text.svg"; width: 18; height: 18 }
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_text.svg"; width: tool.iconSize; height: tool.iconSize }
MouseArea { id: textArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("文字"), parent); onExited: tool.hideTip(qsTr("文字")); onPressed: tool.setActiveTool("text") }
}
Rectangle {
visible: tool.showNumberButton
width: 32; height: 32; radius: 7
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
color: (tool.activeTool === "number") ? "#EFF6FF" : (numberArea.containsMouse ? "#F1F5F9" : "transparent")
border.color: (tool.activeTool === "number") ? "#3B82F6" : "transparent"
border.width: 1
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_list_ordered.svg"; width: 18; height: 18 }
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_list_ordered.svg"; width: tool.iconSize; height: tool.iconSize }
MouseArea { id: numberArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("序号"), parent); onExited: tool.hideTip(qsTr("序号")); onPressed: tool.setActiveTool("number") }
}
Rectangle {
visible: tool.showUndoButton
width: 32; height: 32; radius: 7
width: tool.buttonSize; height: tool.buttonSize; radius: tool.buttonRadius
color: undoArea.containsMouse ? "#EFF6FF" : "transparent"
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_undo.svg"; width: 18; height: 18 }
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_undo.svg"; width: tool.iconSize; height: tool.iconSize }
MouseArea { id: undoArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("撤销"), parent); onExited: tool.hideTip(qsTr("撤销")); onPressed: signalUndo() }
}
}
@@ -246,20 +259,20 @@ Rectangle {
visible: tool.visible && tool.hoverTipText.length > 0
z: 100
parent: tool.parent
width: Math.max(44, tipText.implicitWidth + 14)
height: 24
radius: 6
width: Math.max(36, tipText.implicitWidth + tool.bubblePadding)
height: tool.bubbleHeight
radius: tool.bubbleRadius
color: "#0F172A"
opacity: 0.92
x: Math.max(tool.x, Math.min(tool.x + tool.width - width, tool.x + tool.hoverTipCenterX - width / 2))
y: tool.y + tool.height + 6
y: tool.y + tool.height + tool.bubbleGap
Text {
id: tipText
anchors.centerIn: parent
text: tool.hoverTipText
color: "#FFFFFF"
font.pixelSize: 11
font.pixelSize: tool.tipFontSize
}
}
}
-2
View File
@@ -233,7 +233,6 @@ Window {
)
function createStickyWindow(imageUrl, imgRect) {
console.log("imgRect-->",imgRect)
return windowFactory.createStickyWindow(imageUrl, imgRect)
}
@@ -550,7 +549,6 @@ Window {
Qt.callLater(function() {
var captureRect = normalizedScreenshotArea
var url = O_ScreenCapture.captureRectToStickyUrl(paintBoard, captureRect)
console.log("[Sticky] imageUrl:", url)
O_StickyViewModel.requestSticky(url, captureRect)
resetArea()
})
-264
View File
@@ -1,264 +0,0 @@
import QtQuick
import QtQuick.Window
import QtQuick.Layouts
import QtQuick.Controls.Basic
QtObject {
id: chatPanel
// 对应贴图的图片 URL,由外部绑定
property string imageUrl: ""
signal signalSendPrompt(var text)
// 公开方法
function open(px, py) {
win.x = px
win.y = py
win.visible = true
win.raise()
win.requestActivate()
}
function close() {
win.visible = false
}
// ── 内部 Window ─────────────────────────────────────
property var win: Window {
id: win
flags: Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool
color: "transparent"
width: 420
height: 380
visible: false
Rectangle {
anchors.fill: parent
color: "#ffffff"
radius: 10
border.color: "#E2E8F0"
border.width: 1
ColumnLayout {
anchors.fill: parent
anchors.margins: 12
spacing: 8
// ── 顶部标题栏 ────────────────────────────────────
Rectangle {
Layout.fillWidth: true
height: 36
color: "transparent"
// 拖动区域(z:-1 让关闭按钮的点击不被拦截)
MouseArea {
id: dragHandle
anchors.fill: parent
z: -1
property point startPos
onPressed: startPos = Qt.point(mouseX, mouseY)
onPositionChanged: {
if (pressed) {
win.x += mouseX - startPos.x
win.y += mouseY - startPos.y
}
}
cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor
}
RowLayout {
anchors.fill: parent
spacing: 8
Image {
source: "qrc:/resource/img/lc_chat.svg"
width: 20
height: 20
fillMode: Image.PreserveAspectFit
}
Text {
text: qsTr("AI 图像编辑")
font.pixelSize: 14
font.weight: Font.Medium
color: "#1E293B"
}
Item { Layout.fillWidth: true }
// 关闭按钮
Rectangle {
width: 28
height: 28
radius: 6
color: clearMouse.containsMouse ? "#FEE2E2" : "transparent"
Image {
anchors.centerIn: parent
source: "qrc:/resource/img/lc_close.svg"
width: 14
height: 14
fillMode: Image.PreserveAspectFit
}
MouseArea {
id: clearMouse
anchors.fill: parent
hoverEnabled: true
onClicked: chatPanel.close()
}
}
}
// 标题栏底部分割线
Rectangle {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
height: 1
color: "#E2E8F0"
}
}
// ── 贴图缩略图预览 ────────────────────────────────
Rectangle {
Layout.fillWidth: true
height: 64
radius: 6
color: "#F1F5F9"
border.color: "#E2E8F0"
border.width: 1
clip: true
RowLayout {
anchors.fill: parent
anchors.margins: 8
spacing: 10
// 缩略图
Rectangle {
width: 48
height: 48
radius: 4
color: "#E2E8F0"
clip: true
Image {
anchors.fill: parent
source: chatPanel.imageUrl
fillMode: Image.PreserveAspectCrop
cache: false
}
}
// 文字说明
Column {
Layout.fillWidth: true
spacing: 3
Text {
text: qsTr("当前贴图")
font.pixelSize: 11
color: "#94A3B8"
}
Text {
width: parent.width
text: chatPanel.imageUrl !== ""
? chatPanel.imageUrl.replace(/^image:\/\/[^/]+\//, "ID: ")
: qsTr("未关联贴图")
font.pixelSize: 12
color: "#475569"
elide: Text.ElideRight
}
Text {
text: qsTr("发送后将对此图执行 AI 编辑")
font.pixelSize: 10
color: "#94A3B8"
}
}
}
}
// ── 输入区域 ──────────────────────────────────────
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: "#F8FAFC"
radius: 8
border.color: "#E2E8F0"
border.width: 1
TextArea {
id: input
anchors.fill: parent
anchors.margins: 12
font.family: "Segoe UI"
font.pixelSize: 13
color: "#1E293B"
wrapMode: Text.Wrap
placeholderText: qsTr("描述您想要的修改...")
background: Rectangle { color: "transparent" }
selectByMouse: true
focus: true
}
}
// ── 底部工具栏 ────────────────────────────────────
Rectangle {
Layout.fillWidth: true
height: 40
color: "transparent"
RowLayout {
anchors.fill: parent
spacing: 8
Item { Layout.fillWidth: true }
// 取消按钮
Rectangle {
width: 70; height: 32; radius: 6
color: cancelMouse.containsMouse ? "#F1F5F9" : "#F8FAFC"
border.color: "#E2E8F0"; border.width: 1
Text { anchors.centerIn: parent; text: qsTr("取消"); font.pixelSize: 13; color: "#64748B" }
MouseArea {
id: cancelMouse
anchors.fill: parent; hoverEnabled: true
onClicked: chatPanel.close()
}
}
// 发送按钮
Rectangle {
width: 70; height: 32; radius: 6
color: sendMouse.pressed ? "#0066cc" : (sendMouse.containsMouse ? "#3399ff" : "#0088ff")
RowLayout {
anchors.centerIn: parent; spacing: 4
Image { source: "qrc:/resource/img/lc_send.svg"; width: 14; height: 14 }
Text { text: qsTr("发送"); font.pixelSize: 13; color: "white" }
}
MouseArea {
id: sendMouse
anchors.fill: parent; hoverEnabled: true
onClicked: {
if (input.text.trim() !== "") {
chatPanel.signalSendPrompt(input.text)
chatPanel.close()
}
}
}
}
}
}
}
}
}
}
-194
View File
@@ -1,194 +0,0 @@
import QtQuick 2.12
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.5
// GIF 录制控制栏:在截图选区确定后显示
Rectangle {
id: gifBar
radius: 10
color: "#FFFFFF"
border.color: "#E2E8F0"
border.width: 1
layer.enabled: true
// 外部传入的录制区域
property rect captureRect: Qt.rect(0, 0, 0, 0)
signal signalCancel
signal signalRecordFinished
readonly property bool isRecording: O_GifRecordVM.isRecording
readonly property bool isEncoding: O_GifRecordVM.isEncoding
readonly property int elapsedSecs: O_GifRecordVM.elapsedSecs
readonly property int frameCount: O_GifRecordVM.frameCount
Connections {
target: O_GifRecordVM
function onEncodingFinished(savedPath) {
gifBar.signalRecordFinished()
}
function onEncodingFailed(error) {
console.warn("[GifRecordBar] 录制失败:", error)
gifBar.signalRecordFinished()
}
}
SequentialAnimation {
id: blinkAnim
running: gifBar.isRecording
loops: Animation.Infinite
PropertyAnimation { target: recDot; property: "opacity"; to: 0.2; duration: 500 }
PropertyAnimation { target: recDot; property: "opacity"; to: 1.0; duration: 500 }
}
RowLayout {
anchors.fill: parent
anchors.margins: 6
layoutDirection: Qt.RightToLeft
spacing: 4
// 取消按钮
Rectangle {
width: 32; height: 32; radius: 7
visible: !gifBar.isEncoding
color: cancelArea.containsMouse ? "#FEE2E2" : "transparent"
Behavior on color { ColorAnimation { duration: 100 } }
Image {
anchors.centerIn: parent
source: "qrc:/resource/img/lc_x.svg"
width: 18; height: 18
fillMode: Image.PreserveAspectFit
smooth: true
}
MouseArea {
id: cancelArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
O_GifRecordVM.cancelRecording()
gifBar.signalCancel()
}
}
}
// 停止录制按钮
Rectangle {
width: 32; height: 32; radius: 7
visible: gifBar.isRecording && !gifBar.isEncoding
color: stopArea.containsMouse ? "#FEF3C7" : "transparent"
Behavior on color { ColorAnimation { duration: 100 } }
Image {
anchors.centerIn: parent
source: "qrc:/resource/img/lc_gif_stop.svg"
width: 18; height: 18
fillMode: Image.PreserveAspectFit
smooth: true
layer.enabled: true
layer.effect: null
}
MouseArea {
id: stopArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: O_GifRecordVM.stopRecording()
}
}
Rectangle {
width: 1; height: 20
visible: gifBar.isRecording && !gifBar.isEncoding
color: "#E2E8F0"
Layout.alignment: Qt.AlignVCenter
}
// 录制中:时间 + 帧数
Item {
visible: gifBar.isRecording && !gifBar.isEncoding
Layout.fillWidth: true
height: 32
implicitWidth: recRow.implicitWidth + 8
Row {
id: recRow
anchors.centerIn: parent
spacing: 6
Rectangle {
id: recDot
width: 8; height: 8; radius: 4
color: "#EF4444"
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: {
var m = Math.floor(gifBar.elapsedSecs / 60)
var s = gifBar.elapsedSecs % 60
return (m < 10 ? "0" + m : m) + ":" + (s < 10 ? "0" + s : s)
}
font.pixelSize: 13
font.family: "monospace"
color: "#1E293B"
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: gifBar.frameCount + "f"
font.pixelSize: 11
color: "#94A3B8"
anchors.verticalCenter: parent.verticalCenter
}
}
}
// 编码中:进度提示
Item {
visible: gifBar.isEncoding
Layout.fillWidth: true
height: 32
implicitWidth: encodingRow.implicitWidth + 16
Row {
id: encodingRow
anchors.centerIn: parent
spacing: 8
Rectangle {
width: 16; height: 16
color: "transparent"
anchors.verticalCenter: parent.verticalCenter
Rectangle {
width: 16; height: 16
radius: 8
color: "transparent"
border.color: "#3B82F6"
border.width: 2
Rectangle {
width: 6; height: 6
color: "#3B82F6"
radius: 3
x: 5; y: -1
}
RotationAnimation on rotation {
running: gifBar.isEncoding
from: 0; to: 360
duration: 800
loops: Animation.Infinite
}
}
}
Text {
text: qsTr("正在生成 GIF...")
font.pixelSize: 13
color: "#1E293B"
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
}
-26
View File
@@ -1,26 +0,0 @@
import QtQuick
// 显示当前截图大小
Rectangle {
property string lableText: ""
radius: 6
color: "#1E293B"
opacity: 0.85
border.color: "#334155"
border.width: 1
implicitWidth: labelContent.implicitWidth + 16
implicitHeight: labelContent.implicitHeight + 8
Text {
id: labelContent
anchors.centerIn: parent
font.pixelSize: 12
font.family: "Microsoft YaHei"
color: "#F1F5F9"
text: lableText
renderType: Text.NativeRendering
}
}
-52
View File
@@ -1,52 +0,0 @@
import QtQuick
// ── AI 加载动画层 ─────────────────────────────────────
Rectangle {
width: 100
height: 100
radius: 2
color: "#99000000" // 半透明黑色遮罩
// 旋转圆弧
Item {
id: spinnerRoot
anchors.centerIn: parent
width: 48
height: 48
RotationAnimator {
target: spinnerRoot
from: 0; to: 360
duration: 900
loops: Animation.Infinite
running: O_AIViewModel.isLoading
}
// 四个小圆点模拟旋转圆弧
Repeater {
model: 8
Rectangle {
width: 6
height: 6
radius: 3
color: "white"
opacity: (index + 1) / 8
x: spinnerRoot.width / 2 + 18 * Math.cos(index * Math.PI / 4) - width / 2
y: spinnerRoot.height / 2 + 18 * Math.sin(index * Math.PI / 4) - height / 2
}
}
}
// 提示文字
Text {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: spinnerRoot.bottom
anchors.topMargin: 10
text: "AI 生成中..."
color: "white"
font.pixelSize: 13
}
}
-109
View File
@@ -1,109 +0,0 @@
import QtQuick
import QtQuick.Window
import QtQuick.Layouts
Window {
id: longBar
width: 500
height: 56
visible: false
color: "transparent"
flags: Qt.Tool | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.WindowDoesNotAcceptFocus
signal startRequested()
signal stopRequested()
property bool running: false
property int frameCount: 0
property string message: qsTr("点击开始后,滑动内容自动拼接")
Rectangle {
anchors.fill: parent
radius: 12
color: "#111827"
border.color: "#1F2937"
border.width: 1
RowLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 10
Rectangle {
width: 26
height: 26
radius: 13
color: "#F97316"
Layout.alignment: Qt.AlignVCenter
Text {
anchors.centerIn: parent
text: "长"
color: "#FFFFFF"
font.pixelSize: 14
font.bold: true
}
}
Text {
Layout.fillWidth: true
text: longBar.message
color: "#E5E7EB"
font.pixelSize: 13
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
Text {
text: qsTr("%1 帧").arg(longBar.frameCount)
color: "#D1D5DB"
font.pixelSize: 12
verticalAlignment: Text.AlignVCenter
}
Rectangle {
width: 72
height: 30
radius: 6
visible: !longBar.running
color: startArea.containsMouse ? "#1D4ED8" : "#2563EB"
Layout.alignment: Qt.AlignVCenter
Behavior on color { ColorAnimation { duration: 100 } }
Text {
anchors.centerIn: parent
text: qsTr("开始")
color: "#FFFFFF"
font.pixelSize: 13
}
MouseArea {
id: startArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: longBar.startRequested()
}
}
Rectangle {
width: 72
height: 30
radius: 6
color: stopArea.containsMouse ? "#DC2626" : "#EF4444"
Layout.alignment: Qt.AlignVCenter
Behavior on color { ColorAnimation { duration: 100 } }
Text {
anchors.centerIn: parent
text: longBar.running ? qsTr("完成") : qsTr("关闭")
color: "#FFFFFF"
font.pixelSize: 13
}
MouseArea {
id: stopArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: longBar.stopRequested()
}
}
}
}
}
-53
View File
@@ -1,53 +0,0 @@
import QtQuick
import QtQuick.Window
Window {
id: longFrame
visible: false
color: "transparent"
flags: Qt.Tool | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.WindowTransparentForInput
property rect captureRect: Qt.rect(0, 0, 0, 0)
x: captureRect.x - 2
y: captureRect.y - 2
width: Math.max(0, captureRect.width) + 4
height: Math.max(0, captureRect.height) + 4
Rectangle {
anchors.fill: parent
color: "transparent"
border.color: "#0EA5E9"
border.width: 2
radius: 2
Rectangle {
x: 0
y: 0
width: 6
height: 6
color: "#0EA5E9"
}
Rectangle {
x: parent.width - width
y: 0
width: 6
height: 6
color: "#0EA5E9"
}
Rectangle {
x: 0
y: parent.height - height
width: 6
height: 6
color: "#0EA5E9"
}
Rectangle {
x: parent.width - width
y: parent.height - height
width: 6
height: 6
color: "#0EA5E9"
}
}
}
-82
View File
@@ -1,82 +0,0 @@
import QtQuick
import QtQuick.Window
Window {
id: longPreview
width: 260
height: 420
visible: false
color: "transparent"
flags: Qt.Tool | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.WindowDoesNotAcceptFocus
property string previewUrl: ""
property string statusText: ""
property int frameCount: 0
Rectangle {
anchors.fill: parent
radius: 12
color: "#111827"
border.color: "#1F2937"
border.width: 1
Column {
anchors.fill: parent
anchors.margins: 10
spacing: 8
Row {
width: parent.width
spacing: 8
Text {
text: qsTr("长截图预览")
color: "#F9FAFB"
font.pixelSize: 13
font.bold: true
}
Text {
text: qsTr("%1 帧").arg(longPreview.frameCount)
color: "#9CA3AF"
font.pixelSize: 12
}
}
Rectangle {
width: parent.width
height: parent.height - 58
radius: 8
color: "#0B1220"
border.color: "#334155"
border.width: 1
clip: true
Image {
anchors.fill: parent
anchors.margins: 6
source: longPreview.previewUrl
fillMode: Image.PreserveAspectFit
smooth: true
cache: false
}
Text {
anchors.centerIn: parent
visible: longPreview.previewUrl.length === 0
text: qsTr("暂无预览")
color: "#64748B"
font.pixelSize: 12
}
}
Text {
width: parent.width
text: longPreview.statusText
color: "#CBD5E1"
font.pixelSize: 12
elide: Text.ElideRight
}
}
}
}
-116
View File
@@ -1,116 +0,0 @@
import QtQuick
import QtQuick.Window
// ── 放大镜 ───────────────────────────────────────────────
Rectangle {
id: magnifier
width: 130
height: 170 // 额外 40px 供颜色信息显示
z: 10
color: "transparent"
visible: root.visibility === Window.FullScreen
// 跟随鼠标移动,始终显示(不随截图状态切换)
property int magnifierMouseX: 0
property int magnifierMouseY: 0
property int magnifierAbsX: 0
property int magnifierAbsY: 0
property color magnifierPixelColor: "#000000"
// 跟随鼠标,偏移 +15px,防止被遮挡;超出屏幕时翻转
x: {
var px = magnifierMouseX + 15
if (px + width > root.width) px = magnifierMouseX - width - 15
return px
}
y: {
var py = magnifierMouseY + 15
if (py + height > root.height) py = magnifierMouseY - height - 15
return py
}
// 外框
Rectangle {
id: magnifierFrame
anchors.top: parent.top
width: 130
height: 130
radius: 4
color: "#1a1a1a"
border.color: "#ffffff"
border.width: 1
clip: true
// 放大图像(来自 ImageProvider)
Image {
id: magnifierImage
anchors.centerIn: parent
width: 110
height: 110
// cache: false 确保每次坐标变化时重新请求图像
cache: false
// source 用时间戳 +坐标,强制 QML 重新加载
source: root.visibility === Window.FullScreen
? ("image://magnifier/" + magnifierAbsX + "," + magnifierAbsY + "?t=" + Date.now())
: ""
fillMode: Image.Stretch
}
// 中心像素颜色色块(8×8,叠在图像中心上方,不遮挡准线)
Rectangle {
anchors.centerIn: parent
width: 8
height: 8
color: magnifierPixelColor
border.color: "#ffffff"
border.width: 1
visible: false // 色块已在 RGB 文本行体现,避免遮挡准线
}
}
// 颜色信息栏
Rectangle {
id: colorInfoBar
anchors.top: magnifierFrame.bottom
anchors.topMargin: 2
width: 130
height: 38
radius: 4
color: "#cc1a1a1a"
border.color: "#555555"
border.width: 1
Row {
anchors.centerIn: parent
spacing: 6
// 色块
Rectangle {
width: 16
height: 16
radius: 2
color: magnifierPixelColor
border.color: "#ffffff"
border.width: 1
anchors.verticalCenter: parent.verticalCenter
}
// RGB 文本
Text {
id: rgbText
anchors.verticalCenter: parent.verticalCenter
color: "#ffffff"
font.pixelSize: 11
font.family: "monospace"
text: {
var c = magnifierPixelColor
var r = Math.round(c.r * 255)
var g = Math.round(c.g * 255)
var b = Math.round(c.b * 255)
return "R:" + r + " G:" + g + " B:" + b
}
}
}
}
}
// ── 放大镜 end ───────────────────────────────────────────
-257
View File
@@ -1,257 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Rectangle {
id: panel
property color currentColor: "#F43F5E"
property int currentSize: 6
property bool showColorRow: true
property string toolName: ""
property string annotationText: qsTr("文本")
property bool textBackgroundEnabled: true
property int mosaicBlurLevel: 2
property bool numberAutoIncrement: true
property int numberValue: 1
width: 260
height: toolName === "text" ? 128
: (toolName === "number" ? 132
: ((toolName === "mosaic") ? 92 : (showColorRow ? 84 : 50)))
radius: 10
color: "#FFFFFF"
border.color: "#E2E8F0"
border.width: 1
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 8
RowLayout {
spacing: 6
Layout.fillWidth: true
visible: panel.showColorRow
height: panel.showColorRow ? implicitHeight : 0
Repeater {
model: ["#F43F5E","#EF4444","#F97316","#EAB308",
"#22C55E","#3B82F6","#8B5CF6","#000000","#FFFFFF"]
delegate: Rectangle {
width: 18; height: 18; radius: 9
color: modelData
border.color: panel.currentColor === modelData ? "#3B82F6" : "#CBD5E1"
border.width: panel.currentColor === modelData ? 2 : 1
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onPressed: panel.currentColor = modelData
}
}
}
}
RowLayout {
spacing: 6
Layout.fillWidth: true
Repeater {
model: [
{ size: 2, label: qsTr("细") },
{ size: 4, label: qsTr("中") },
{ size: 6, label: qsTr("粗") },
{ size: 10, label: qsTr("特粗") }
]
delegate: Rectangle {
width: 48; height: 28
radius: 6
color: panel.currentSize === modelData.size ? "#EFF6FF" : (sizeArea.containsMouse ? "#F1F5F9" : "transparent")
border.color: panel.currentSize === modelData.size ? "#3B82F6" : "#E2E8F0"
border.width: 1
Column {
anchors.centerIn: parent
spacing: 3
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
width: 24
height: Math.min(modelData.size, 6)
radius: height / 2
color: panel.currentSize === modelData.size ? "#3B82F6" : "#94A3B8"
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: modelData.label
font.pixelSize: 10
color: panel.currentSize === modelData.size ? "#3B82F6" : "#64748B"
}
}
MouseArea {
id: sizeArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: panel.currentSize = modelData.size
}
}
}
}
RowLayout {
visible: panel.toolName === "text"
Layout.fillWidth: true
spacing: 8
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 34
radius: 6
color: "#F8FAFC"
border.color: textInput.activeFocus ? "#3B82F6" : "#E2E8F0"
border.width: 1
TextInput {
id: textInput
anchors.fill: parent
anchors.margins: 8
verticalAlignment: TextInput.AlignVCenter
color: "#0F172A"
font.pixelSize: 13
selectByMouse: true
text: panel.annotationText
onTextChanged: panel.annotationText = text
}
Text {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 8
visible: textInput.text.length === 0 && !textInput.activeFocus
text: qsTr("输入文字")
color: "#94A3B8"
font.pixelSize: 13
}
}
CheckBox {
text: qsTr("背景")
checked: panel.textBackgroundEnabled
onToggled: panel.textBackgroundEnabled = checked
}
}
RowLayout {
visible: panel.toolName === "mosaic"
Layout.fillWidth: true
spacing: 8
Text {
text: qsTr("模糊")
color: "#334155"
}
Slider {
Layout.fillWidth: true
from: 1
to: 4
stepSize: 1
value: panel.mosaicBlurLevel
onMoved: panel.mosaicBlurLevel = Math.round(value)
}
Text {
text: panel.mosaicBlurLevel
color: "#64748B"
}
}
ColumnLayout {
visible: panel.toolName === "number"
Layout.fillWidth: true
spacing: 8
CheckBox {
text: qsTr("自动递增")
checked: panel.numberAutoIncrement
onToggled: panel.numberAutoIncrement = checked
}
RowLayout {
Layout.fillWidth: true
spacing: 8
Text {
text: qsTr("数字")
color: "#334155"
}
Rectangle {
width: 28
height: 28
radius: 6
color: minusArea.containsMouse ? "#F1F5F9" : "transparent"
border.color: "#CBD5E1"
border.width: 1
Text {
anchors.centerIn: parent
text: "-"
color: "#334155"
font.pixelSize: 16
}
MouseArea {
id: minusArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: panel.numberValue = Math.max(1, panel.numberValue - 1)
}
}
Rectangle {
Layout.fillWidth: true
height: 28
radius: 6
color: "#F8FAFC"
border.color: "#E2E8F0"
border.width: 1
Text {
anchors.centerIn: parent
text: panel.numberValue
color: "#0F172A"
font.pixelSize: 13
}
}
Rectangle {
width: 28
height: 28
radius: 6
color: plusArea.containsMouse ? "#F1F5F9" : "transparent"
border.color: "#CBD5E1"
border.width: 1
Text {
anchors.centerIn: parent
text: "+"
color: "#334155"
font.pixelSize: 16
}
MouseArea {
id: plusArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: panel.numberValue = Math.min(9999, panel.numberValue + 1)
}
}
}
}
}
}
-328
View File
@@ -1,328 +0,0 @@
import QtQuick
import QtQuick.Window
import QtQuick.Controls
import QtQuick.Layouts
// TZOcrResult
// -----------
// 无边框悬浮窗,展示 OCR 识别结果。
// 由 TZWindow.qml 通过 Qt.createComponent 动态创建并显示。
Window {
id: ocrWin
flags: Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool
color: "transparent"
width: 420
height: 320
// 初始居中(创建后由调用方设置位置,此处给默认值)
x: (Screen.width - width) / 2
y: (Screen.height - height) / 2
Component.onCompleted: {
ocrWin.raise()
ocrWin.requestActivate()
}
Shortcut {
sequence: "Esc"
onActivated: ocrWin.close()
}
// ── 阴影容器 ──────────────────────────────────────────
Rectangle {
id: shadow
anchors.fill: parent
anchors.margins: 10
radius: 12
color: "white"
layer.enabled: true
border.color: "#D1D5DB"
border.width: 1
// ── 标题栏 ─────────────────────────────────────────
Rectangle {
id: titleBar
anchors.top: shadow.top
anchors.left: shadow.left
anchors.right: shadow.right
height: 40
radius: 12
color: "#F8FAFC"
// 圆角只在上半部分(下方盖住)
Rectangle {
anchors.bottom: titleBar.bottom
anchors.left: titleBar.left
anchors.right: titleBar.right
height: titleBar.radius
color: titleBar.color
}
// 标题文字
Text {
anchors.verticalCenter: titleBar.verticalCenter
anchors.left: titleBar.left
anchors.leftMargin: 14
text: qsTr("OCR 识别结果")
font.pixelSize: 14
font.weight: Font.Medium
color: "#1E293B"
}
// 关闭按钮
Rectangle {
id: closeBtn
anchors.right: titleBar.right
anchors.rightMargin: 8
anchors.verticalCenter: titleBar.verticalCenter
width: 26; height: 26; radius: 5
color: closeArea.containsMouse ? "#FEE2E2" : "transparent"
Behavior on color { ColorAnimation { duration: 80 } }
Text {
anchors.centerIn: parent
text: "✕"
color: closeArea.containsMouse ? "#DC2626" : "#94A3B8"
font.pixelSize: 13
}
MouseArea {
id: closeArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: ocrWin.close()
}
}
// 拖动标题栏移动窗口
MouseArea {
anchors.fill: titleBar
anchors.rightMargin: 40
property point pressPos
onPressed: function(mouse) {
pressPos = Qt.point(mouse.x, mouse.y)
}
onPositionChanged: function(mouse) {
if (pressed) {
ocrWin.x += mouse.x - pressPos.x
ocrWin.y += mouse.y - pressPos.y
}
}
}
}
// ── 分割线 ─────────────────────────────────────────
Rectangle {
id: divider
anchors.top: titleBar.bottom
anchors.left: shadow.left
anchors.right: shadow.right
height: 1
color: "#E2E8F0"
}
// ── 内容区 ─────────────────────────────────────────
Item {
anchors.top: divider.bottom
anchors.left: shadow.left
anchors.right: shadow.right
anchors.bottom: bottomBar.top
// Loading 遮罩
Rectangle {
anchors.fill: parent
color: "#F8FAFC"
visible: O_OcrVM.isRecognizing
radius: 6
Item {
id: spinnerRoot
anchors.centerIn: parent
width: 48; height: 48
RotationAnimator {
target: spinnerRoot
from: 0; to: 360
duration: 900
loops: Animation.Infinite
running: O_OcrVM.isRecognizing
}
Repeater {
model: 8
Rectangle {
width: 6; height: 6; radius: 3
color: "#3B82F6"
opacity: (index + 1) / 8
x: spinnerRoot.width / 2 + 18 * Math.cos(index * Math.PI / 4) - width / 2
y: spinnerRoot.height / 2 + 18 * Math.sin(index * Math.PI / 4) - height / 2
}
}
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: spinnerRoot.bottom
anchors.topMargin: 12
text: qsTr("正在识别中…")
color: "#64748B"
font.pixelSize: 13
}
}
// 文本区域(识别完成后显示)
ScrollView {
anchors.fill: parent
anchors.margins: 8
visible: !O_OcrVM.isRecognizing
clip: true
TextArea {
id: resultArea
text: O_OcrVM.resultText
readOnly: false // 允许用户二次编辑
wrapMode: TextEdit.Wrap
font.pixelSize: 14
color: "#1E293B"
background: null
padding: 4
selectByMouse: true
placeholderText: qsTr("识别结果将显示在这里…")
placeholderTextColor: "#94A3B8"
}
}
}
// ── 底部操作栏 ─────────────────────────────────────
Rectangle {
id: bottomBar
anchors.bottom: shadow.bottom
anchors.left: shadow.left
anchors.right: shadow.right
height: 44
radius: 12
color: "#F8FAFC"
// 圆角只在下半部分
Rectangle {
anchors.top: bottomBar.top
anchors.left: bottomBar.left
anchors.right: bottomBar.right
height: bottomBar.radius
color: bottomBar.color
}
// 分割线
Rectangle {
anchors.top: bottomBar.top
anchors.left: bottomBar.left
anchors.right: bottomBar.right
height: 1
color: "#E2E8F0"
}
Row {
anchors.centerIn: parent
spacing: 10
// 一键复制
Rectangle {
id: copyBtn
width: 100; height: 30; radius: 6
color: copyArea.containsMouse ? "#DBEAFE" : "#EFF6FF"
border.color: "#3B82F6"
border.width: 1
Behavior on color { ColorAnimation { duration: 80 } }
Text {
anchors.centerIn: parent
text: qsTr("复制全部")
color: "#3B82F6"
font.pixelSize: 13
}
MouseArea {
id: copyArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
O_OcrVM.copyResultToClipboard()
copyFeedbackTimer.start()
}
}
}
// 关闭
Rectangle {
width: 70; height: 30; radius: 6
color: closeBtn2Area.containsMouse ? "#F1F5F9" : "transparent"
border.color: "#CBD5E1"
border.width: 1
Behavior on color { ColorAnimation { duration: 80 } }
Text {
anchors.centerIn: parent
text: qsTr("关闭")
color: "#64748B"
font.pixelSize: 13
}
MouseArea {
id: closeBtn2Area
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: ocrWin.close()
}
}
}
}
// 复制成功短暂提示
Rectangle {
id: copyFeedback
anchors.horizontalCenter: shadow.horizontalCenter
anchors.bottom: bottomBar.top
anchors.bottomMargin: 6
width: 100; height: 28; radius: 6
color: "#22C55E"
visible: false
opacity: 0
Text {
anchors.centerIn: parent
text: qsTr("已复制!")
color: "white"
font.pixelSize: 12
}
Timer {
id: copyFeedbackTimer
interval: 1200
onTriggered: copyFeedback.visible = false
}
onVisibleChanged: {
if (visible) opacity = 1
}
Behavior on opacity {
NumberAnimation { duration: 300 }
}
}
Connections {
target: copyFeedbackTimer
function onTriggered() {
copyFeedback.opacity = 0
}
}
}
// 启动计时后立即显示反馈
Connections {
target: copyArea
function onClicked() {
copyFeedback.visible = true
}
}
}
-14
View File
@@ -1,14 +0,0 @@
import QtQuick
Rectangle {
// id:leftTop
// x: screenshotArea.x - handleSize/2
// y: screenshotArea.y - handleSize/2
property int handleSize: 8
width: handleSize
height: handleSize
radius: handleSize/2
color: "#0088FF"
border.color: "white"
border.width: 1
}
-296
View File
@@ -1,296 +0,0 @@
import QtQuick
import QtQuick.Window
Rectangle{
id:handleGroup
property rect area: Qt.rect(0,0,0,0)
property rect initialArea: Qt.rect(0,0,0,0)
property var mousePointStart: Qt.point(0,0)
x:area.x
y:area.y
width: area.width
height: area.height
color: "transparent"
//左上角控制点
TZResizeHandle
{
id:leftTop
x: - handleSize/2
y: - handleSize/2
// 仅当选区宽度/高度非零时显示
visible: area.width !== 0 || area.height !== 0
MouseArea {
anchors.fill: parent
cursorShape: Qt.SizeFDiagCursor
onPressed: {
let globalPos = leftTop.mapToItem(null, mouseX, mouseY);
mousePointStart.x = globalPos.x
mousePointStart.y = globalPos.y
initialArea = area
}
onReleased: {
// 重置所有临时变量
mousePointStart = Qt.point(0,0)
initialArea = area
}
onPositionChanged: {
if (pressed) {
let globalPos = leftTop.mapToItem(null, mouseX, mouseY);
let dx = globalPos.x - mousePointStart.x
let dy = globalPos.y - mousePointStart.y
area.x = initialArea.x + dx
area.y = initialArea.y + dy
area.width = initialArea.width - dx
area.height = initialArea.height - dy
}
}
}
}
//正上方控制点
TZResizeHandle
{
id: top
x: (area.width - handleSize/2)/2
y: -handleSize/2
// 仅当选区宽度/高度非零时显示
visible: area.width !== 0 || area.height !== 0
MouseArea {
anchors.fill: parent
cursorShape: Qt.SizeVerCursor
onPressed: {
let globalPos = top.mapToItem(null, mouseX, mouseY);
mousePointStart.y = globalPos.y
initialArea = area
}
onReleased: {
// 重置所有临时变量
mousePointStart = Qt.point(0,0)
initialArea = area
}
onPositionChanged: {
if (pressed) {
let globalPos = top.mapToItem(null, mouseX, mouseY);
let dy = globalPos.y - mousePointStart.y
area.y = initialArea.y + dy
area.height = initialArea.height - dy
}
}
}
}
//右上角控制点
TZResizeHandle
{
id:rightTop
x: area.width - handleSize/2
y: -handleSize/2
visible: area.width !== 0 || area.height !== 0
MouseArea {
anchors.fill: parent
cursorShape: Qt.SizeBDiagCursor
onPressed: {
let globalPos = rightTop.mapToItem(null, mouseX, mouseY);
mousePointStart.x = globalPos.x
mousePointStart.y = globalPos.y
initialArea = area
}
onReleased: {
mousePointStart = Qt.point(0,0)
initialArea = area
}
onPositionChanged: {
if (pressed) {
let globalPos = rightTop.mapToItem(null, mouseX, mouseY);
let dx = globalPos.x - mousePointStart.x
let dy = globalPos.y - mousePointStart.y
area.y = initialArea.y + dy
area.width = initialArea.width + dx
area.height = initialArea.height - dy
}
}
}
}
//右边控制点
TZResizeHandle
{
id: right
x: area.width - handleSize/2
y: (area.height -handleSize/2)/2
visible: area.width !== 0 || area.height !== 0
MouseArea {
anchors.fill: parent
cursorShape: Qt.SizeHorCursor
onPressed: {
let globalPos = right.mapToItem(null, mouseX, mouseY);
mousePointStart.x = globalPos.x
initialArea = area
}
onReleased: {
mousePointStart = Qt.point(0,0)
initialArea = area
}
onPositionChanged: {
if (pressed) {
let globalPos = right.mapToItem(null, mouseX, mouseY);
let dx = globalPos.x - mousePointStart.x
area.width = initialArea.width + dx
}
}
}
}
//右下角控制点
TZResizeHandle
{
id:rightButtom
x: area.width - handleSize/2
y: area.height - handleSize/2
visible: area.width !== 0 || area.height !== 0
z:2
MouseArea {
anchors.fill: parent
cursorShape: Qt.SizeFDiagCursor
onPressed: {
let globalPos = rightButtom.mapToItem(null, mouseX, mouseY);
mousePointStart.x = globalPos.x
mousePointStart.y = globalPos.y
initialArea = area
}
onReleased: {
mousePointStart = Qt.point(0,0)
initialArea = area
}
onPositionChanged: {
if (pressed) {
let globalPos = rightButtom.mapToItem(null, mouseX, mouseY);
let dx = globalPos.x - mousePointStart.x
let dy = globalPos.y - mousePointStart.y
area.width = initialArea.width + dx
area.height = initialArea.height + dy
}
}
}
}
//正下方控制点
TZResizeHandle
{
id:buttom
x: (area.width - handleSize/2)/2
y: area.height - handleSize/2
visible: area.width !== 0 || area.height !== 0
z:2
MouseArea {
anchors.fill: parent
cursorShape: Qt.SizeVerCursor
onPressed: {
let globalPos = buttom.mapToItem(null, mouseX, mouseY);
mousePointStart.y = globalPos.y
initialArea = area
}
onReleased: {
mousePointStart = Qt.point(0,0)
initialArea = area
}
onPositionChanged: {
if (pressed) {
let globalPos = buttom.mapToItem(null, mouseX, mouseY);
let dy = globalPos.y - mousePointStart.y
area.height = initialArea.height + dy
}
}
}
}
//左下角控制点
TZResizeHandle
{
id:leftBottom
x: -handleSize/2
y: area.height - handleSize/2
visible: area.width !== 0 || area.height !== 0
z:2
MouseArea {
anchors.fill: parent
cursorShape: Qt.SizeBDiagCursor
onPressed: {
let globalPos = leftBottom.mapToItem(null, mouseX, mouseY);
mousePointStart.x = globalPos.x
mousePointStart.y = globalPos.y
initialArea = area
}
onReleased: {
mousePointStart = Qt.point(0,0)
initialArea = area
}
onPositionChanged: {
if (pressed) {
let globalPos = leftBottom.mapToItem(null, mouseX, mouseY);
let dx = globalPos.x - mousePointStart.x
let dy = globalPos.y - mousePointStart.y
area.x = initialArea.x + dx
area.width = initialArea.width - dx
area.height = initialArea.height + dy
}
}
}
}
//左边控制点
TZResizeHandle
{
id:left
x: -handleSize/2
y: (area.height - handleSize/2)/2
visible: area.width !== 0 || area.height !== 0
z:2
MouseArea {
anchors.fill: parent
cursorShape: Qt.SizeHorCursor
onPressed: {
let globalPos = left.mapToItem(null, mouseX, mouseY);
mousePointStart.x = globalPos.x
initialArea = area
}
onReleased: {
mousePointStart = Qt.point(0,0)
initialArea = area
}
onPositionChanged: {
if (pressed) {
let globalPos = left.mapToItem(null, mouseX, mouseY);
let dx = globalPos.x - mousePointStart.x
area.x = initialArea.x + dx
area.width = initialArea.width - dx
}
}
}
}
}
-124
View File
@@ -1,124 +0,0 @@
import QtQuick
import Qt.labs.platform 1.0 as Platform
import QtCore
import QtQml
Item {
id: root
visible: false
required property var menuPrefs
required property string imageUrl
required property real imgOpacity
required property real zoomFactor
required property bool toolbarVisible
required property url saveFolder
required property var overwriteWithEdits
required property var copyImageToClipboard
required property var openChatPanel
required property var hideOrToggleToolbar
required property var setOpacityValue
required property var setZoomValue
required property var rotateCurrent
required property var mirrorCurrent
required property var resetDisplaySize
required property var resetWindowPosition
required property var saveOriginalImage
required property var saveMergedTo
required property var closeWindow
property bool contextMenuOpen: false
function open() {
contextMenu.open()
}
Platform.Menu {
id: contextMenu
onAboutToShow: root.contextMenuOpen = true
onAboutToHide: root.contextMenuOpen = false
Platform.MenuItem { text: qsTr("当前透明度 %1%").arg(Math.round(root.imgOpacity * 100)); enabled: false }
Platform.MenuSeparator {}
Platform.MenuItem { visible: root.menuPrefs.pinSaveMerged; text: qsTr("覆盖贴图(合并标注)"); onTriggered: root.overwriteWithEdits() }
Platform.MenuItem { visible: root.menuPrefs.pinCopy; text: qsTr("复制贴图"); onTriggered: root.copyImageToClipboard(root.imageUrl) }
Platform.MenuItem { visible: root.menuPrefs.pinAi; text: qsTr("AI 编辑"); onTriggered: root.openChatPanel() }
Platform.MenuItem {
visible: root.menuPrefs.pinToolbar
text: root.toolbarVisible ? qsTr("隐藏工具栏") : qsTr("显示工具栏")
onTriggered: root.hideOrToggleToolbar()
}
Platform.MenuItem { visible: root.menuPrefs.pinClose; text: qsTr("关闭窗口"); onTriggered: root.closeWindow() }
Platform.MenuSeparator {}
Platform.MenuItem { text: qsTr("透明度 +5%"); onTriggered: root.setOpacityValue(root.imgOpacity + 0.05) }
Platform.MenuItem { text: qsTr("透明度 -5%"); onTriggered: root.setOpacityValue(root.imgOpacity - 0.05) }
Platform.MenuSeparator {}
Platform.MenuItem { text: qsTr("缩小"); onTriggered: root.setZoomValue(root.zoomFactor - 0.1) }
Platform.MenuItem { text: qsTr("放大"); onTriggered: root.setZoomValue(root.zoomFactor + 0.1) }
Platform.MenuItem { text: qsTr("旋转 90°"); onTriggered: root.rotateCurrent(90) }
Platform.MenuItem { text: qsTr("镜像"); onTriggered: root.mirrorCurrent() }
Platform.MenuItem { text: qsTr("恢复原始大小"); onTriggered: root.resetDisplaySize() }
Platform.MenuSeparator {}
Platform.MenuItem {
text: qsTr("重置位置")
onTriggered: root.resetWindowPosition()
}
Platform.MenuSeparator {}
Platform.MenuItem {
text: root.toolbarVisible ? qsTr("隐藏工具栏") : qsTr("显示工具栏")
onTriggered: root.hideOrToggleToolbar()
}
Platform.MenuItem { text: qsTr("AI 编辑"); onTriggered: root.openChatPanel() }
Platform.MenuSeparator {}
Platform.MenuItem { text: qsTr("保存原图"); onTriggered: saveDialog.open() }
Platform.MenuItem { text: qsTr("另存为(合并标注)"); onTriggered: mergedSaveDialog.open() }
Platform.MenuItem { text: qsTr("覆盖贴图(合并标注)"); onTriggered: root.overwriteWithEdits() }
Platform.MenuItem { text: qsTr("复制贴图"); onTriggered: root.copyImageToClipboard(root.imageUrl) }
Platform.MenuSeparator {}
Platform.Menu {
title: qsTr("常用项置顶")
Platform.MenuItem { text: qsTr("覆盖贴图"); checkable: true; checked: root.menuPrefs.pinSaveMerged; onTriggered: root.menuPrefs.pinSaveMerged = !root.menuPrefs.pinSaveMerged }
Platform.MenuItem { text: qsTr("复制贴图"); checkable: true; checked: root.menuPrefs.pinCopy; onTriggered: root.menuPrefs.pinCopy = !root.menuPrefs.pinCopy }
Platform.MenuItem { text: qsTr("AI 编辑"); checkable: true; checked: root.menuPrefs.pinAi; onTriggered: root.menuPrefs.pinAi = !root.menuPrefs.pinAi }
Platform.MenuItem { text: qsTr("显示/隐藏工具栏"); checkable: true; checked: root.menuPrefs.pinToolbar; onTriggered: root.menuPrefs.pinToolbar = !root.menuPrefs.pinToolbar }
Platform.MenuItem { text: qsTr("关闭窗口"); checkable: true; checked: root.menuPrefs.pinClose; onTriggered: root.menuPrefs.pinClose = !root.menuPrefs.pinClose }
}
Platform.MenuSeparator {}
Platform.MenuItem { text: qsTr("关闭窗口"); onTriggered: root.closeWindow() }
}
Platform.FileDialog {
id: saveDialog
title: qsTr("选择保存位置")
folder: root.saveFolder
fileMode: Platform.FileDialog.SaveFile
nameFilters: ["PNG (*.png)", "JPG (*.jpg)", "BMP (*.bmp)"]
onAccepted: root.saveOriginalImage(root.imageUrl, saveDialog.file)
}
Platform.FileDialog {
id: mergedSaveDialog
title: qsTr("另存为(合并标注)")
folder: root.saveFolder
fileMode: Platform.FileDialog.SaveFile
nameFilters: ["PNG (*.png)", "JPG (*.jpg)", "BMP (*.bmp)"]
onAccepted: root.saveMergedTo(mergedSaveDialog.file)
}
}
-747
View File
@@ -1,747 +0,0 @@
import QtQuick
import QtQuick.Window
import Qt.labs.platform 1.0 as Platform
import QtQuick.Effects
import QtQuick.Controls.Basic
import QtCore
import CustomComponents 1.0
Window {
id: stickyWin
flags: Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool
color: "transparent"
readonly property real minOpacity: 0.1
readonly property real maxOpacity: 1.0
readonly property real minZoomFactor: 0.3
readonly property real maxZoomFactor: 4.0
readonly property int toolbarButtonSize: 30
readonly property int toolbarIconSize: 18
readonly property color toolbarHoverColor: "#F1F5F9"
readonly property color toolbarActiveColor: "#EFF6FF"
readonly property color toolbarActiveBorderColor: "#3B82F6"
property string imageUrl: ""
property rect imgRect: Qt.rect(0, 0, 0, 0)
property bool contentActive: true
property bool cleanedUp: false
property real imgOpacity: 1.0
property string activeTool: ""
property bool toolbarVisible: true
property string toolbarHoverTipText: ""
property real toolbarHoverTipCenterX: 0
property bool contextMenuOpen: stickyContextMenu.contextMenuOpen
property int imageRevision: 0
property real zoomFactor: 1.0
property real dpiScale: 1.0
property size sourceImageSize: Qt.size(1, 1)
property int imageLogicalWidth: 1
property int imageLogicalHeight: 1
readonly property int toolbarHeight: 40
readonly property int toolbarGap: 8
readonly property int toolbarTipGap: 6
readonly property int toolbarTipHeight: 24
readonly property int shadowRadius: 12
readonly property int shadowOffset: 4
readonly property int imageLeft: imgRect.width >= 0 ? imgRect.x : imgRect.x + imgRect.width
readonly property int imageTop: imgRect.height >= 0 ? imgRect.y : imgRect.y + imgRect.height
readonly property int displayW: Math.max(1, Math.round(imageLogicalWidth * zoomFactor))
readonly property int displayH: Math.max(1, Math.round(imageLogicalHeight * zoomFactor))
readonly property int toolbarContentWidth: Math.max(560, stickyRow.implicitWidth + 16)
readonly property string displaySourceUrl: {
if (typeof stickyWin.imageUrl !== "string" || stickyWin.imageUrl.length === 0)
return ""
return stickyWin.imageUrl + "?rev=" + stickyWin.imageRevision
}
readonly property var stickyShapeMap: ({
pencil: 0,
rect: 1,
circle: 2,
arrow: 3,
mosaic: 4
})
readonly property var drawToolButtons: [
{ tool: "pencil", tip: qsTr("Pencil"), icon: "qrc:/resource/img/lc_pencil.svg" },
{ tool: "rect", tip: qsTr("Rectangle"), icon: "qrc:/resource/img/lc_square.svg" },
{ tool: "circle", tip: qsTr("Circle"), icon: "qrc:/resource/img/lc_circle.svg" },
{ tool: "arrow", tip: qsTr("Arrow"), icon: "qrc:/resource/img/lc_arrow.svg" },
{ tool: "mosaic", tip: qsTr("Mosaic"), icon: "qrc:/resource/img/lc_mosaic.svg" }
]
readonly property var zoomActionButtons: [
{ action: "zoomOut", tip: qsTr("Zoom out"), text: "-", textSize: 16 },
{ action: "zoomIn", tip: qsTr("Zoom in"), text: "+", textSize: 16 }
]
readonly property var transformActionButtons: [
{ action: "rotate", tip: qsTr("Rotate 90°"), text: "↻", textSize: 14 },
{ action: "mirror", tip: qsTr("Mirror"), text: "⇋", textSize: 14 },
{ action: "resetSize", tip: qsTr("Reset size"), text: "1:1", textSize: 10 }
]
readonly property var editActionButtons: [
{ action: "doneAndCopy", tip: qsTr("Done and copy"), icon: "qrc:/resource/img/lc_check.svg", hoverColor: "#E0F2FE" },
{ action: "undo", tip: qsTr("Undo"), icon: "qrc:/resource/img/lc_undo.svg", hoverColor: toolbarActiveColor },
{ action: "hideToolbar", tip: qsTr("Hide toolbar"), icon: "qrc:/resource/img/lc_x.svg", hoverColor: "#FEE2E2" }
]
readonly property int stickyShapeType: {
return stickyShapeMap[activeTool] !== undefined ? stickyShapeMap[activeTool] : 1
}
width: Math.max(displayW + shadowRadius * 2, toolbarContentWidth + 16)
height: displayH + shadowRadius * 2 + shadowOffset + toolbarGap + toolbarHeight + toolbarTipGap + toolbarTipHeight + 4
x: imageLeft - shadowRadius
y: imageTop - shadowRadius
Settings {
id: menuPrefs
category: "sticky_menu"
property bool pinSaveMerged: true
property bool pinCopy: true
property bool pinAi: false
property bool pinToolbar: false
property bool pinClose: false
}
function setActiveTool(name) {
if (activeTool === name)
activeTool = ""
else
activeTool = name
}
function clamp(value, minValue, maxValue) {
return Math.max(minValue, Math.min(maxValue, value))
}
function hasValidImage(image) {
return image && image.width > 0 && image.height > 0
}
function clearActiveTool() {
stickyWin.activeTool = ""
}
function setToolbarVisible(visible) {
stickyWin.toolbarVisible = visible
if (!visible)
clearActiveTool()
}
function toggleToolbarVisible() {
setToolbarVisible(!stickyWin.toolbarVisible)
}
function showToolbarTip(text, item) {
toolbarHoverTipText = text
if (item) {
var p = stickyToolbar.mapFromItem(item, item.width / 2, item.height)
toolbarHoverTipCenterX = p.x
} else {
toolbarHoverTipCenterX = stickyToolbar.width / 2
}
}
function hideToolbarTip(text) {
if (text === undefined || toolbarHoverTipText === text)
toolbarHoverTipText = ""
}
function setOpacityValue(v) {
stickyWin.imgOpacity = clamp(v, minOpacity, maxOpacity)
}
function setZoomValue(v) {
zoomFactor = clamp(v, minZoomFactor, maxZoomFactor)
}
function resetDisplaySize() {
zoomFactor = 1.0
}
function updateImageMetricsFromStore() {
var img = O_StickyViewModel.getImageByUrl(stickyWin.imageUrl)
if (hasValidImage(img)) {
stickyPaintBoard.setBackgroundImg(img)
} else {
syncPaintboardBackgroundFromDisplay()
}
}
function syncPaintboardBackgroundFromDisplay() {
if (!stickyWin.visible || !stickyWin.contentActive)
return
if (stickyImage.status !== Image.Ready)
return
stickyImage.grabToImage(function(result) {
if (!hasValidImage(result && result.image))
return
stickyPaintBoard.setBackgroundImg(result.image)
}, Qt.size(Math.max(1, Math.abs(imgRect.width)), Math.max(1, Math.abs(imgRect.height))))
}
function refreshAfterImageChanged() {
imageRevision += 1
stickyPaintBoard.reset()
updateImageMetricsFromStore()
}
function renderAnnotationLayer() {
return stickyPaintBoard.renderToImage()
}
function overwriteWithEdits() {
var layer = renderAnnotationLayer()
var ok = O_StickyViewModel.overwriteWithAnnotations(stickyWin.imageUrl, layer)
if (ok)
refreshAfterImageChanged()
}
function rotateCurrent(degrees) {
if (O_StickyViewModel.rotateImage(stickyWin.imageUrl, degrees))
refreshAfterImageChanged()
}
function mirrorCurrent() {
if (O_StickyViewModel.mirrorImage(stickyWin.imageUrl, true, false))
refreshAfterImageChanged()
}
function saveMergedTo(pathUrl) {
var layer = renderAnnotationLayer()
O_StickyViewModel.saveMergedImage(stickyWin.imageUrl, layer, pathUrl)
}
function completeEditsAndCopy() {
var layer = renderAnnotationLayer()
if (hasValidImage(layer))
overwriteWithEdits()
O_StickyViewModel.copyImageToClipboard(stickyWin.imageUrl)
setToolbarVisible(false)
}
function ensureMosaicBackground() {
var img = O_StickyViewModel.getImageByUrl(stickyWin.imageUrl)
if (hasValidImage(img))
stickyPaintBoard.setBackgroundImg(img)
setActiveTool("mosaic")
if (stickyWin.activeTool !== "mosaic" || stickyPaintBoard.hasBackgroundImg())
return
Qt.callLater(function() {
var retryImg = O_StickyViewModel.getImageByUrl(stickyWin.imageUrl)
if (hasValidImage(retryImg))
stickyPaintBoard.setBackgroundImg(retryImg)
if (!stickyPaintBoard.hasBackgroundImg())
stickyWin.syncPaintboardBackgroundFromDisplay()
})
}
function handleToolbarAction(action) {
switch (action) {
case "zoomOut":
setZoomValue(stickyWin.zoomFactor - 0.1)
break
case "zoomIn":
setZoomValue(stickyWin.zoomFactor + 0.1)
break
case "rotate":
rotateCurrent(90)
break
case "mirror":
mirrorCurrent()
break
case "resetSize":
resetDisplaySize()
break
case "doneAndCopy":
completeEditsAndCopy()
break
case "undo":
stickyPaintBoard.undo()
break
case "hideToolbar":
setToolbarVisible(false)
break
case "ocr":
var img = O_StickyViewModel.getImageByUrl(stickyWin.imageUrl)
O_OcrVM.recognize(img)
break
}
}
function handleDrawTool(tool) {
if (tool === "mosaic") {
ensureMosaicBackground()
return
}
setActiveTool(tool)
}
function resetWindowPosition() {
stickyWin.x = stickyWin.imageLeft - stickyWin.shadowRadius
stickyWin.y = stickyWin.imageTop - stickyWin.shadowRadius
}
function cleanup() {
if (cleanedUp)
return
cleanedUp = true
stickyWin.visible = false
contentActive = false
stickyWin.releaseResources()
O_StickyViewModel.releaseImage(imageUrl)
imageUrl = ""
}
function updateDpiScale() {
dpiScale = (screen && screen.devicePixelRatio > 0) ? screen.devicePixelRatio : 1.0
}
function updateImageLogicalSize() {
sourceImageSize = O_StickyViewModel.getImageSizeByUrl(stickyWin.imageUrl)
if (sourceImageSize.width > 0 && sourceImageSize.height > 0) {
imageLogicalWidth = Math.max(1, Math.round(sourceImageSize.width / dpiScale))
imageLogicalHeight = Math.max(1, Math.round(sourceImageSize.height / dpiScale))
}
}
Component.onCompleted: {
updateDpiScale()
updateImageMetricsFromStore()
updateImageLogicalSize()
stickyWin.raise()
stickyWin.requestActivate()
}
Shortcut {
sequence: "Esc"
onActivated: {
if (stickyWin.activeTool !== "") {
clearActiveTool()
return
}
if (stickyWin.toolbarVisible) {
setToolbarVisible(false)
return
}
stickyWin.close()
}
}
onImageUrlChanged: {
imageRevision = 0
sourceImageSize = Qt.size(1, 1)
imageLogicalWidth = 1
imageLogicalHeight = 1
resetDisplaySize()
refreshAfterImageChanged()
}
onActiveToolChanged: {
if (activeTool === "mosaic")
updateImageMetricsFromStore()
}
onScreenChanged: {
updateDpiScale()
updateImageLogicalSize()
}
Component.onDestruction: cleanup()
function onClosing(close) {
close.accepted = true
chatPanel.close()
cleanup()
Qt.callLater(function() { stickyWin.destroy() })
}
Rectangle {
id: imgContainer
visible: stickyWin.contentActive
z: 10
x: stickyWin.shadowRadius
y: stickyWin.shadowRadius + stickyWin.shadowOffset / 2
width: stickyWin.displayW
height: stickyWin.displayH
radius: 2
clip: true
color: "transparent"
Item {
id: visualStage
anchors.centerIn: parent
width: stickyWin.imageLogicalWidth
height: stickyWin.imageLogicalHeight
scale: stickyWin.zoomFactor
Image {
id: stickyImage
anchors.fill: parent
source: stickyWin.displaySourceUrl
fillMode: Image.Stretch
opacity: stickyWin.imgOpacity
cache: false
onStatusChanged: {
if (status === Image.Ready)
stickyWin.updateImageLogicalSize()
if (status === Image.Ready && stickyWin.activeTool === "mosaic" && !stickyPaintBoard.hasBackgroundImg())
stickyWin.syncPaintboardBackgroundFromDisplay()
}
}
PaintBoard {
id: stickyPaintBoard
anchors.fill: parent
z: 20
visible: stickyWin.contentActive
enabled: stickyWin.activeTool !== ""
shapeType: stickyWin.stickyShapeType
penColor: stickyMarkTool.currentColor
penSize: stickyMarkTool.currentSize
mosaicBlurLevel: stickyMarkTool.mosaicBlurLevel
}
}
}
MultiEffect {
visible: stickyWin.contentActive
z: 5
source: stickyImage
anchors.fill: imgContainer
shadowEnabled: true
shadowColor: "#55000000"
shadowBlur: stickyWin.shadowRadius / 20.0
shadowVerticalOffset: stickyWin.shadowOffset
shadowHorizontalOffset: 0
opacity: stickyWin.imgOpacity
}
TZLoadingSpinner {
anchors.centerIn: imgContainer
width: stickyWin.displayW
height: stickyWin.displayH
visible: O_AIViewModel.isLoading && stickyWin.contentActive
}
TZMarkToolInfo {
id: stickyMarkTool
z: 32
visible: stickyWin.toolbarVisible && stickyWin.activeTool !== ""
toolName: stickyWin.activeTool
showColorRow: stickyWin.activeTool !== "mosaic"
x: Math.max(8, stickyToolbar.x)
y: stickyToolbar.y - height - 6
}
Rectangle {
id: stickyToolbar
z: 30
visible: stickyWin.toolbarVisible
width: stickyWin.toolbarContentWidth
height: stickyWin.toolbarHeight
radius: 10
color: "#FFFFFF"
border.color: "#E2E8F0"
border.width: 1
x: Math.max(8, Math.min(stickyWin.width - width - 8, stickyWin.shadowRadius + (stickyWin.displayW - width) / 2))
y: stickyWin.shadowRadius + stickyWin.shadowOffset / 2 + stickyWin.displayH + stickyWin.toolbarGap
Row {
id: stickyRow
anchors.centerIn: parent
spacing: 6
Rectangle {
width: stickyWin.toolbarButtonSize; height: stickyWin.toolbarButtonSize; radius: 7
color: ocrArea.containsMouse ? "#F0FDF4" : "transparent"
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_ocr.svg"; width: stickyWin.toolbarIconSize; height: stickyWin.toolbarIconSize }
MouseArea {
id: ocrArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: stickyWin.showToolbarTip(qsTr("OCR"), parent)
onExited: stickyWin.hideToolbarTip(qsTr("OCR"))
onPressed: stickyWin.handleToolbarAction("ocr")
}
}
Rectangle { width: 1; height: 18; color: "#E2E8F0"; anchors.verticalCenter: parent.verticalCenter }
Repeater {
model: stickyWin.drawToolButtons
delegate: Rectangle {
property var buttonData: modelData
width: stickyWin.toolbarButtonSize
height: stickyWin.toolbarButtonSize
radius: 7
color: stickyWin.activeTool === buttonData.tool ? stickyWin.toolbarActiveColor : (hoverArea.containsMouse ? stickyWin.toolbarHoverColor : "transparent")
border.color: stickyWin.activeTool === buttonData.tool ? stickyWin.toolbarActiveBorderColor : "transparent"
border.width: 1
Image {
anchors.centerIn: parent
source: buttonData.icon
width: stickyWin.toolbarIconSize
height: stickyWin.toolbarIconSize
}
MouseArea {
id: hoverArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: stickyWin.showToolbarTip(parent.buttonData.tip, parent)
onExited: stickyWin.hideToolbarTip(parent.buttonData.tip)
onPressed: stickyWin.handleDrawTool(parent.buttonData.tool)
}
}
}
Rectangle { width: 1; height: 18; color: "#E2E8F0"; anchors.verticalCenter: parent.verticalCenter }
Repeater {
model: [stickyWin.zoomActionButtons[0]]
delegate: Rectangle {
property var buttonData: modelData
width: stickyWin.toolbarButtonSize
height: stickyWin.toolbarButtonSize
radius: 7
color: hoverArea.containsMouse ? stickyWin.toolbarHoverColor : "transparent"
Text {
anchors.centerIn: parent
text: parent.buttonData.text
color: "#334155"
font.pixelSize: parent.buttonData.textSize
}
MouseArea {
id: hoverArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: stickyWin.showToolbarTip(parent.buttonData.tip, parent)
onExited: stickyWin.hideToolbarTip(parent.buttonData.tip)
onPressed: stickyWin.handleToolbarAction(parent.buttonData.action)
}
}
}
Rectangle { width: 40; height: 30; radius: 7; color: "transparent"; Text { anchors.centerIn: parent; text: Math.round(stickyWin.zoomFactor * 100) + "%"; color: "#64748B"; font.pixelSize: 11 } }
Repeater {
model: [stickyWin.zoomActionButtons[1]].concat(stickyWin.transformActionButtons)
delegate: Rectangle {
property var buttonData: modelData
width: stickyWin.toolbarButtonSize
height: stickyWin.toolbarButtonSize
radius: 7
color: hoverArea.containsMouse ? stickyWin.toolbarHoverColor : "transparent"
Text {
anchors.centerIn: parent
text: parent.buttonData.text
color: "#334155"
font.pixelSize: parent.buttonData.textSize
}
MouseArea {
id: hoverArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: stickyWin.showToolbarTip(parent.buttonData.tip, parent)
onExited: stickyWin.hideToolbarTip(parent.buttonData.tip)
onPressed: stickyWin.handleToolbarAction(parent.buttonData.action)
}
}
}
Repeater {
model: stickyWin.editActionButtons
delegate: Rectangle {
property var buttonData: modelData
width: stickyWin.toolbarButtonSize
height: stickyWin.toolbarButtonSize
radius: 7
color: hoverArea.containsMouse ? buttonData.hoverColor : "transparent"
Image {
anchors.centerIn: parent
source: buttonData.icon
width: stickyWin.toolbarIconSize
height: stickyWin.toolbarIconSize
}
MouseArea {
id: hoverArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: stickyWin.showToolbarTip(parent.buttonData.tip, parent)
onExited: stickyWin.hideToolbarTip(parent.buttonData.tip)
onPressed: stickyWin.handleToolbarAction(parent.buttonData.action)
}
}
}
}
}
Rectangle {
id: stickyToolbarTip
parent: stickyWin.contentItem
visible: stickyWin.toolbarVisible && stickyWin.toolbarHoverTipText.length > 0
z: 40
width: Math.min(stickyWin.width - 8, Math.max(64, stickyToolbarTipText.implicitWidth + 16))
height: 24
radius: 6
color: "#0F172A"
opacity: 0.92
x: Math.max(4, Math.min(stickyWin.width - width - 4, stickyToolbar.x + stickyWin.toolbarHoverTipCenterX - width / 2))
y: Math.min(stickyWin.height - height - 4, stickyToolbar.y + stickyToolbar.height + stickyWin.toolbarTipGap)
Text {
id: stickyToolbarTipText
anchors.centerIn: parent
text: stickyWin.toolbarHoverTipText
color: "#FFFFFF"
font.pixelSize: 11
}
}
readonly property int chatPanelGap: 6
TZChat {
id: chatPanel
imageUrl: stickyWin.imageUrl
onSignalSendPrompt: function(text) {
O_AIViewModel.sendPrompt(text, stickyWin.imageUrl)
}
}
Connections {
target: O_AIViewModel
function onSignalRequestComplete(oldImageUrl, newImageUrl) {
if (oldImageUrl !== stickyWin.imageUrl)
return
var old = oldImageUrl
stickyWin.imageUrl = newImageUrl
Qt.callLater(function() { O_StickyViewModel.releaseImage(old) })
}
function onSignalRequestFailed(errorMsg) {
console.warn("[TZStickyWindow] AI request failed: " + errorMsg)
}
}
function openChatPanel() {
var imgLeft = stickyWin.x + stickyWin.shadowRadius
var imgTop = stickyWin.y + stickyWin.shadowRadius
var imgBottom = imgTop + stickyWin.displayH
var screenH = Screen.desktopAvailableHeight
var screenW = Screen.desktopAvailableWidth
var cx = imgLeft
if (cx + chatPanel.win.width > screenW)
cx = imgLeft + stickyWin.displayW - chatPanel.win.width
var cy
if (imgBottom + chatPanelGap + chatPanel.win.height <= screenH)
cy = imgBottom + chatPanelGap
else
cy = imgTop - chatPanelGap - chatPanel.win.height
chatPanel.open(Math.max(0, cx), Math.max(0, cy))
}
TZStickyContextMenu {
id: stickyContextMenu
menuPrefs: menuPrefs
imageUrl: stickyWin.imageUrl
imgOpacity: stickyWin.imgOpacity
zoomFactor: stickyWin.zoomFactor
toolbarVisible: stickyWin.toolbarVisible
saveFolder: O_ImageSaver.savePath
overwriteWithEdits: function() { stickyWin.overwriteWithEdits() }
copyImageToClipboard: function(url) { O_StickyViewModel.copyImageToClipboard(url) }
openChatPanel: function() { stickyWin.openChatPanel() }
hideOrToggleToolbar: function() { stickyWin.toggleToolbarVisible() }
setOpacityValue: function(value) { stickyWin.setOpacityValue(value) }
setZoomValue: function(value) { stickyWin.setZoomValue(value) }
rotateCurrent: function(degrees) { stickyWin.rotateCurrent(degrees) }
mirrorCurrent: function() { stickyWin.mirrorCurrent() }
resetDisplaySize: function() { stickyWin.resetDisplaySize() }
resetWindowPosition: function() { stickyWin.resetWindowPosition() }
saveOriginalImage: function(url, pathUrl) { O_StickyViewModel.saveImage(url, pathUrl) }
saveMergedTo: function(pathUrl) { stickyWin.saveMergedTo(pathUrl) }
closeWindow: function() { stickyWin.close() }
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
enabled: stickyWin.activeTool === "" && !stickyWin.contextMenuOpen
property point dragPos: Qt.point(0, 0)
property bool leftDragActive: false
z: 0
function inImageArea(mx, my) {
return mx >= stickyWin.shadowRadius
&& mx <= stickyWin.shadowRadius + stickyWin.displayW
&& my >= stickyWin.shadowRadius + stickyWin.shadowOffset / 2
&& my <= stickyWin.shadowRadius + stickyWin.shadowOffset / 2 + stickyWin.displayH
}
onPressed: function(mouse) {
if (mouse.button === Qt.LeftButton) {
dragPos = Qt.point(mouseX, mouseY)
leftDragActive = true
} else {
leftDragActive = false
}
}
onPositionChanged: function(mouse) {
if (pressed && leftDragActive && (mouse.buttons & Qt.LeftButton) && stickyWin.activeTool === "" && !stickyWin.contextMenuOpen) {
stickyWin.x += mouseX - dragPos.x
stickyWin.y += mouseY - dragPos.y
}
}
onReleased: function(mouse) {
if (!(mouse.buttons & Qt.LeftButton))
leftDragActive = false
}
onDoubleClicked: function(mouse) {
if (mouse.button === Qt.LeftButton && inImageArea(mouseX, mouseY))
resetDisplaySize()
}
onClicked: function(mouse) {
if (mouse.button === Qt.RightButton) {
leftDragActive = false
stickyContextMenu.open()
}
}
onWheel: function(wheel) {
if (wheel.angleDelta.y > 0)
setZoomValue(stickyWin.zoomFactor + 0.1)
else if (wheel.angleDelta.y < 0)
setZoomValue(stickyWin.zoomFactor - 0.1)
wheel.accepted = true
}
}
}
-230
View File
@@ -1,230 +0,0 @@
import QtQuick 2.12
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.5
Rectangle {
id: tool
radius: 10
color: "#FFFFFF"
visible: false
border.color: "#E2E8F0"
border.width: 1
layer.enabled: true
signal signalScreenshot
signal signalCancel
signal signalSave
signal signalSticky
signal signalUndo
signal signalGifRecord
signal signalLongScreenshot
signal signalOcr
property string activeTool: ""
property bool markToolInfoVisible: false
property string hoverTipText: ""
property real hoverTipCenterX: width / 2
readonly property int shapeType: {
switch (activeTool) {
case "pencil": return 0
case "rect": return 1
case "circle": return 2
case "arrow": return 3
case "mosaic": return 4
case "text": return 5
case "number": return 6
case "highlight": return 7
default: return 1
}
}
function setActiveTool(name) {
if (activeTool === name) {
activeTool = ""
markToolInfoVisible = false
} else {
activeTool = name
markToolInfoVisible = true
}
}
function showTip(text, targetItem) {
hoverTipText = text
if (targetItem) {
var p = tool.mapFromItem(targetItem, targetItem.width / 2, targetItem.height)
hoverTipCenterX = p.x
} else {
hoverTipCenterX = width / 2
}
}
function hideTip(text) {
if (text === undefined || hoverTipText === text)
hoverTipText = ""
}
RowLayout {
anchors.fill: parent
anchors.margins: 6
layoutDirection: Qt.RightToLeft
spacing: 2
Rectangle {
width: 32; height: 32; radius: 7
color: checkArea.containsMouse ? "#DCFCE7" : "transparent"
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_check.svg"; width: 18; height: 18 }
MouseArea { id: checkArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("完成"), parent); onExited: tool.hideTip(qsTr("完成")); onPressed: signalScreenshot() }
}
Rectangle {
width: 32; height: 32; radius: 7
color: closeArea.containsMouse ? "#FEE2E2" : "transparent"
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_x.svg"; width: 18; height: 18 }
MouseArea { id: closeArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("取消"), parent); onExited: tool.hideTip(qsTr("取消")); onPressed: signalCancel() }
}
Rectangle { width: 1; height: 20; color: "#E2E8F0"; Layout.alignment: Qt.AlignVCenter }
Rectangle {
width: 32; height: 32; radius: 7
color: ocrArea.containsMouse ? "#F0FDF4" : "transparent"
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_ocr.svg"; width: 18; height: 18 }
MouseArea { id: ocrArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("OCR"), parent); onExited: tool.hideTip(qsTr("OCR")); onPressed: signalOcr() }
}
Rectangle {
id: gifRecordBtn
width: 32; height: 32; radius: 7
color: gifArea.containsMouse ? "#FFF7ED" : "transparent"
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_gif_record.svg"; width: 18; height: 18 }
MouseArea { id: gifArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("录制GIF"), parent); onExited: tool.hideTip(qsTr("录制GIF")); onPressed: signalGifRecord() }
}
Rectangle {
width: 32; height: 32; radius: 7
color: longShotArea.containsMouse ? "#FFF7ED" : "transparent"
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_longshot.svg"; width: 18; height: 18 }
MouseArea { id: longShotArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("长截图"), parent); onExited: tool.hideTip(qsTr("长截图")); onPressed: signalLongScreenshot() }
}
Rectangle { width: 1; height: 20; color: "#E2E8F0"; Layout.alignment: Qt.AlignVCenter }
Rectangle {
width: 32; height: 32; radius: 7
color: saveArea.containsMouse ? "#EFF6FF" : "transparent"
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_save.svg"; width: 18; height: 18 }
MouseArea { id: saveArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("保存"), parent); onExited: tool.hideTip(qsTr("保存")); onPressed: signalSave() }
}
Rectangle {
width: 32; height: 32; radius: 7
color: fixArea.containsMouse ? "#EFF6FF" : "transparent"
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_pin.svg"; width: 18; height: 18 }
MouseArea { id: fixArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("贴图"), parent); onExited: tool.hideTip(qsTr("贴图")); onPressed: signalSticky() }
}
Rectangle { width: 1; height: 20; color: "#E2E8F0"; Layout.alignment: Qt.AlignVCenter }
Rectangle {
width: 32; height: 32; radius: 7
color: (tool.activeTool === "rect") ? "#EFF6FF" : (rectArea.containsMouse ? "#F1F5F9" : "transparent")
border.color: (tool.activeTool === "rect") ? "#3B82F6" : "transparent"
border.width: 1
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_square.svg"; width: 18; height: 18 }
MouseArea { id: rectArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("矩形"), parent); onExited: tool.hideTip(qsTr("矩形")); onPressed: tool.setActiveTool("rect") }
}
Rectangle {
width: 32; height: 32; radius: 7
color: (tool.activeTool === "circle") ? "#EFF6FF" : (circleArea.containsMouse ? "#F1F5F9" : "transparent")
border.color: (tool.activeTool === "circle") ? "#3B82F6" : "transparent"
border.width: 1
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_circle.svg"; width: 18; height: 18 }
MouseArea { id: circleArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("圆形"), parent); onExited: tool.hideTip(qsTr("圆形")); onPressed: tool.setActiveTool("circle") }
}
Rectangle {
width: 32; height: 32; radius: 7
color: (tool.activeTool === "arrow") ? "#EFF6FF" : (arrowArea.containsMouse ? "#F1F5F9" : "transparent")
border.color: (tool.activeTool === "arrow") ? "#3B82F6" : "transparent"
border.width: 1
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_arrow.svg"; width: 18; height: 18 }
MouseArea { id: arrowArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("箭头"), parent); onExited: tool.hideTip(qsTr("箭头")); onPressed: tool.setActiveTool("arrow") }
}
Rectangle {
width: 32; height: 32; radius: 7
color: (tool.activeTool === "pencil") ? "#EFF6FF" : (pencilArea.containsMouse ? "#F1F5F9" : "transparent")
border.color: (tool.activeTool === "pencil") ? "#3B82F6" : "transparent"
border.width: 1
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_pencil.svg"; width: 18; height: 18 }
MouseArea { id: pencilArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("画笔"), parent); onExited: tool.hideTip(qsTr("画笔")); onPressed: tool.setActiveTool("pencil") }
}
Rectangle {
width: 32; height: 32; radius: 7
color: (tool.activeTool === "highlight") ? "#EFF6FF" : (highlightArea.containsMouse ? "#F1F5F9" : "transparent")
border.color: (tool.activeTool === "highlight") ? "#3B82F6" : "transparent"
border.width: 1
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_highlighter.svg"; width: 18; height: 18 }
MouseArea { id: highlightArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("高亮"), parent); onExited: tool.hideTip(qsTr("高亮")); onPressed: tool.setActiveTool("highlight") }
}
Rectangle {
width: 32; height: 32; radius: 7
color: (tool.activeTool === "mosaic") ? "#EFF6FF" : (mosaicArea.containsMouse ? "#F1F5F9" : "transparent")
border.color: (tool.activeTool === "mosaic") ? "#3B82F6" : "transparent"
border.width: 1
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_mosaic.svg"; width: 18; height: 18 }
MouseArea { id: mosaicArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("马赛克"), parent); onExited: tool.hideTip(qsTr("马赛克")); onPressed: tool.setActiveTool("mosaic") }
}
Rectangle {
width: 32; height: 32; radius: 7
color: (tool.activeTool === "text") ? "#EFF6FF" : (textArea.containsMouse ? "#F1F5F9" : "transparent")
border.color: (tool.activeTool === "text") ? "#3B82F6" : "transparent"
border.width: 1
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_text.svg"; width: 18; height: 18 }
MouseArea { id: textArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("文字"), parent); onExited: tool.hideTip(qsTr("文字")); onPressed: tool.setActiveTool("text") }
}
Rectangle {
width: 32; height: 32; radius: 7
color: (tool.activeTool === "number") ? "#EFF6FF" : (numberArea.containsMouse ? "#F1F5F9" : "transparent")
border.color: (tool.activeTool === "number") ? "#3B82F6" : "transparent"
border.width: 1
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_list_ordered.svg"; width: 18; height: 18 }
MouseArea { id: numberArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("序号"), parent); onExited: tool.hideTip(qsTr("序号")); onPressed: tool.setActiveTool("number") }
}
Rectangle {
width: 32; height: 32; radius: 7
color: undoArea.containsMouse ? "#EFF6FF" : "transparent"
Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_undo.svg"; width: 18; height: 18 }
MouseArea { id: undoArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: tool.showTip(qsTr("撤销"), parent); onExited: tool.hideTip(qsTr("撤销")); onPressed: signalUndo() }
}
}
Rectangle {
id: tipBubble
visible: tool.visible && tool.hoverTipText.length > 0
z: 100
parent: tool.parent
width: Math.max(44, tipText.implicitWidth + 14)
height: 24
radius: 6
color: "#0F172A"
opacity: 0.92
x: Math.max(tool.x, Math.min(tool.x + tool.width - width, tool.x + tool.hoverTipCenterX - width / 2))
y: tool.y + tool.height + 6
Text {
id: tipText
anchors.centerIn: parent
text: tool.hoverTipText
color: "#FFFFFF"
font.pixelSize: 11
}
}
}
-1054
View File
File diff suppressed because it is too large. Load diff
+55 -9
View File
@@ -12,7 +12,9 @@ QtObject {
signal signalSendPrompt(var text)
// 公开方法
function open(px, py) {
function open(px, py, targetScreen) {
if (targetScreen)
win.screen = targetScreen
win.x = px
win.y = py
win.visible = true
@@ -56,14 +58,10 @@ QtObject {
id: dragHandle
anchors.fill: parent
z: -1
property point startPos
onPressed: startPos = Qt.point(mouseX, mouseY)
onPositionChanged: {
if (pressed) {
win.x += mouseX - startPos.x
win.y += mouseY - startPos.y
}
onPressed: function(mouse) {
mouse.accepted = true
win.startSystemMove()
}
cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor
}
@@ -187,6 +185,8 @@ QtObject {
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumHeight: 140
Layout.preferredHeight: 180
color: "#F8FAFC"
radius: 8
border.color: "#E2E8F0"
@@ -259,7 +259,53 @@ QtObject {
}
}
}
Rectangle {
id: resizeHandle
width: 16
height: 16
radius: 8
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.rightMargin: 8
anchors.bottomMargin: 8
color: resizeMouse.pressed ? "#0369A1" : "#0EA5E9"
border.color: "white"
border.width: 1
z: 10
Canvas {
anchors.fill: parent
anchors.margins: 3
onPaint: {
var ctx = getContext("2d")
ctx.reset()
ctx.strokeStyle = "white"
ctx.lineWidth = 1.2
ctx.beginPath()
ctx.moveTo(width * 0.25, height)
ctx.lineTo(width, height * 0.25)
ctx.stroke()
ctx.beginPath()
ctx.moveTo(width * 0.55, height)
ctx.lineTo(width, height * 0.55)
ctx.stroke()
}
}
MouseArea {
id: resizeMouse
anchors.fill: parent
cursorShape: Qt.SizeFDiagCursor
onPressed: function(mouse) {
mouse.accepted = true
win.startSystemResize(Qt.RightEdge | Qt.BottomEdge)
}
}
}
}
}
}
-52
View File
@@ -1,52 +0,0 @@
import QtQuick
// ── AI 加载动画层 ─────────────────────────────────────
Rectangle {
width: 100
height: 100
radius: 2
color: "#99000000" // 半透明黑色遮罩
// 旋转圆弧
Item {
id: spinnerRoot
anchors.centerIn: parent
width: 48
height: 48
RotationAnimator {
target: spinnerRoot
from: 0; to: 360
duration: 900
loops: Animation.Infinite
running: O_AIViewModel.isLoading
}
// 四个小圆点模拟旋转圆弧
Repeater {
model: 8
Rectangle {
width: 6
height: 6
radius: 3
color: "white"
opacity: (index + 1) / 8
x: spinnerRoot.width / 2 + 18 * Math.cos(index * Math.PI / 4) - width / 2
y: spinnerRoot.height / 2 + 18 * Math.sin(index * Math.PI / 4) - height / 2
}
}
}
// 提示文字
Text {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: spinnerRoot.bottom
anchors.topMargin: 10
text: "AI 生成中..."
color: "white"
font.pixelSize: 13
}
}
-40
View File
@@ -1,40 +0,0 @@
import QtQuick
Rectangle {
id: bar
radius: 10
color: "#FFFFFF"
border.color: "#E2E8F0"
border.width: 1
signal zoomOut()
signal zoomIn()
signal rotate90()
signal mirror()
signal resetSize()
signal doneAndCopy()
signal hideToolbar()
signal hoverEntered(string text, Item targetItem)
signal hoverExited(string text)
property real zoomFactor: 1.0
width: extraRow.implicitWidth + 16
height: 40
Row {
id: extraRow
anchors.centerIn: parent
spacing: 6
Rectangle { width: 30; height: 30; radius: 7; color: zoomOutArea.containsMouse ? "#F1F5F9" : "transparent"; Text { anchors.centerIn: parent; text: "-"; color: "#334155"; font.pixelSize: 16 } MouseArea { id: zoomOutArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: bar.hoverEntered(qsTr("Zoom out"), parent); onExited: bar.hoverExited(qsTr("Zoom out")); onPressed: bar.zoomOut() } }
Rectangle { width: 40; height: 30; radius: 7; color: "transparent"; Text { anchors.centerIn: parent; text: Math.round(bar.zoomFactor * 100) + "%"; color: "#64748B"; font.pixelSize: 11 } }
Rectangle { width: 30; height: 30; radius: 7; color: zoomInArea.containsMouse ? "#F1F5F9" : "transparent"; Text { anchors.centerIn: parent; text: "+"; color: "#334155"; font.pixelSize: 16 } MouseArea { id: zoomInArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: bar.hoverEntered(qsTr("Zoom in"), parent); onExited: bar.hoverExited(qsTr("Zoom in")); onPressed: bar.zoomIn() } }
Rectangle { width: 30; height: 30; radius: 7; color: rotateArea.containsMouse ? "#F1F5F9" : "transparent"; Text { anchors.centerIn: parent; text: "↻"; color: "#334155"; font.pixelSize: 14 } MouseArea { id: rotateArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: bar.hoverEntered(qsTr("Rotate 90°"), parent); onExited: bar.hoverExited(qsTr("Rotate 90°")); onPressed: bar.rotate90() } }
Rectangle { width: 30; height: 30; radius: 7; color: mirrorArea.containsMouse ? "#F1F5F9" : "transparent"; Text { anchors.centerIn: parent; text: "⇋"; color: "#334155"; font.pixelSize: 14 } MouseArea { id: mirrorArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: bar.hoverEntered(qsTr("Mirror"), parent); onExited: bar.hoverExited(qsTr("Mirror")); onPressed: bar.mirror() } }
Rectangle { width: 30; height: 30; radius: 7; color: resetSizeArea.containsMouse ? "#F1F5F9" : "transparent"; Text { anchors.centerIn: parent; text: "1:1"; color: "#334155"; font.pixelSize: 10 } MouseArea { id: resetSizeArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: bar.hoverEntered(qsTr("Reset size"), parent); onExited: bar.hoverExited(qsTr("Reset size")); onPressed: bar.resetSize() } }
Rectangle { width: 1; height: 18; color: "#E2E8F0"; anchors.verticalCenter: parent.verticalCenter }
Rectangle { width: 30; height: 30; radius: 7; color: overwriteArea.containsMouse ? "#E0F2FE" : "transparent"; Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_check.svg"; width: 18; height: 18 } MouseArea { id: overwriteArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: bar.hoverEntered(qsTr("Done and copy"), parent); onExited: bar.hoverExited(qsTr("Done and copy")); onPressed: bar.doneAndCopy() } }
Rectangle { width: 30; height: 30; radius: 7; color: clearToolArea.containsMouse ? "#FEE2E2" : "transparent"; Image { anchors.centerIn: parent; source: "qrc:/resource/img/lc_x.svg"; width: 18; height: 18 } MouseArea { id: clearToolArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onEntered: bar.hoverEntered(qsTr("Hide toolbar"), parent); onExited: bar.hoverExited(qsTr("Hide toolbar")); onPressed: bar.hideToolbar() } }
}
}
+199 -402
View File
@@ -1,317 +1,173 @@
import QtQuick
import QtQuick.Window
import Qt5Compat.GraphicalEffects
import Qt.labs.platform 1.0 as Platform
import QtQuick.Effects
import QtQuick.Controls.Basic
import QtCore
import CustomComponents 1.0
import ".."
import "../Capture"
import CustomComponents 1.0
Window {
id: stickyWin
flags: Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool
color: "transparent"
visible: true
property string imageUrl: ""
property rect imgRect: Qt.rect(0, 0, 0, 0)
property bool contentActive: true
property bool cleanedUp: false
property real imgOpacity: 1.0
property string activeTool: ""
property bool toolbarVisible: true
property string toolbarHoverTipText: ""
property real toolbarHoverTipCenterX: 0
property bool contextMenuOpen: stickyContextMenu.contextMenuOpen
property int imageRevision: 0
property real zoomFactor: 1.0
property real dpiScale: 1.0
property size sourceImageSize: Qt.size(1, 1)
property int imageLogicalWidth: 1
property int imageLogicalHeight: 1
readonly property int toolbarHeight: 40
property bool toolbarVisible: true
property real zoomFactor: 1.0
property int windowWidthValue: 1
property int windowHeightValue: 1
readonly property int shadowPadding: 12
readonly property color shadowColor: "#2F8FFF"
readonly property int toolbarHeight: toolBar.implicitHeight
readonly property int toolbarGap: 8
readonly property int toolbarTipGap: 6
readonly property int toolbarTipHeight: 24
readonly property int shadowRadius: 12
readonly property int shadowOffset: 4
readonly property int displayW: Math.max(1, Math.round(imageLogicalWidth * zoomFactor))
readonly property int displayH: Math.max(1, Math.round(imageLogicalHeight * zoomFactor))
readonly property int extraToolbarWidth: stickyExtraBar.width
readonly property int toolbarContentWidth: stickyTool.implicitWidth + extraToolbarWidth + 6
readonly property int toolbarBlockX: Math.max(8, Math.min(stickyWin.width - toolbarContentWidth - 8, imgContainer.x + (imgContainer.width - toolbarContentWidth) / 2))
readonly property int toolbarTopY: imgContainer.y + imgContainer.height + stickyWin.toolbarGap
readonly property int toolbarTipReserve: 30
readonly property int markToolGap: 8
readonly property int contentX: stickyWin.shadowPadding
readonly property int contentY: stickyWin.shadowPadding
readonly property int contentWidth: Math.max(1, stickyWin.width - stickyWin.shadowPadding * 2)
readonly property int contentHeight: Math.max(1, stickyWin.height - stickyWin.shadowPadding * 2
- (stickyWin.toolbarVisible ? (stickyWin.toolbarHeight + stickyWin.toolbarGap + stickyWin.toolbarTipReserve) : 0))
readonly property int stickyShapeType: {
switch (activeTool) {
case "pencil": return 0
case "rect": return 1
case "circle": return 2
case "arrow": return 3
case "mosaic": return 4
default: return 1
}
width: windowWidthValue
height: windowHeightValue
minimumWidth: windowWidthValue
maximumWidth: windowWidthValue
minimumHeight: windowHeightValue
maximumHeight: windowHeightValue
function updateImageSize() {
sourceImageSize = O_StickyViewModel.getImageSizeByUrl(imageUrl)
if (!sourceImageSize || sourceImageSize.width <= 0 || sourceImageSize.height <= 0)
sourceImageSize = Qt.size(1, 1)
updateWindowSize()
}
width: Math.max(displayW, toolbarContentWidth + 16)
height: displayH + toolbarGap + toolbarHeight + toolbarTipGap + toolbarTipHeight + 4
function syncInitialPosition() {
O_StickyViewModel.positionStickyWindow(stickyWin, imgRect)
function updateWindowSize() {
var imageWidth = Math.max(1, Math.floor(sourceImageSize.width * zoomFactor))
var imageHeight = Math.max(1, Math.floor(sourceImageSize.height * zoomFactor))
windowWidthValue = imageWidth + shadowPadding * 2
windowHeightValue = imageHeight + shadowPadding * 2
+ (toolbarVisible ? (toolbarHeight + toolbarGap + toolbarTipReserve) : 0)
Qt.callLater(applyNativeSize)
}
function updateDpiScale() {
dpiScale = (screen && screen.devicePixelRatio > 0) ? screen.devicePixelRatio : 1.0
function applyNativeSize() {
O_StickyViewModel.resizeStickyWindow(stickyWin, windowWidthValue, windowHeightValue)
}
function updateImageLogicalSize() {
sourceImageSize = O_StickyViewModel.getImageSizeByUrl(stickyWin.imageUrl)
if (sourceImageSize.width > 0 && sourceImageSize.height > 0) {
imageLogicalWidth = Math.max(1, Math.round(sourceImageSize.width / dpiScale))
imageLogicalHeight = Math.max(1, Math.round(sourceImageSize.height / dpiScale))
}
}
Settings {
id: menuPrefs
category: "sticky_menu"
property bool pinSaveMerged: true
property bool pinCopy: true
property bool pinAi: false
property bool pinToolbar: false
property bool pinClose: false
}
function showToolbarTip(text, item) {
toolbarHoverTipText = text
if (item) {
var p = stickyExtraBar.mapFromItem(item, item.width / 2, item.height)
toolbarHoverTipCenterX = p.x
} else {
toolbarHoverTipCenterX = stickyExtraBar.width / 2
}
}
function hideToolbarTip(text) {
if (text === undefined || toolbarHoverTipText === text)
toolbarHoverTipText = ""
}
function displaySourceUrl() {
if (typeof stickyWin.imageUrl !== "string" || stickyWin.imageUrl.length === 0)
return ""
return stickyWin.imageUrl + "?rev=" + stickyWin.imageRevision
}
function setOpacityValue(v) {
stickyWin.imgOpacity = Math.max(0.1, Math.min(1.0, v))
}
function setZoomValue(v) {
zoomFactor = Math.max(0.3, Math.min(4.0, v))
function setZoomFactor(value) {
zoomFactor = Math.max(0.3, Math.min(4.0, value))
updateWindowSize()
}
function resetDisplaySize() {
zoomFactor = 1.0
setZoomFactor(1.0)
}
function updateImageMetricsFromStore() {
var img = O_StickyViewModel.getImageByUrl(stickyWin.imageUrl)
if (img && img.width > 0 && img.height > 0) {
stickyPaintBoard.setBackgroundImg(img)
} else {
syncPaintboardBackgroundFromDisplay()
}
}
function syncPaintboardBackgroundFromDisplay() {
if (!stickyWin.visible || !stickyWin.contentActive)
return
if (stickyImage.status !== Image.Ready)
return
stickyImage.grabToImage(function(result) {
if (!result || !result.image || result.image.width <= 0 || result.image.height <= 0)
return
stickyPaintBoard.setBackgroundImg(result.image)
}, Qt.size(Math.max(1, Math.abs(imgRect.width)), Math.max(1, Math.abs(imgRect.height))))
}
function refreshAfterImageChanged() {
imageRevision += 1
stickyPaintBoard.reset()
updateImageMetricsFromStore()
}
function overwriteWithEdits() {
var layer = stickyPaintBoard.renderToImage()
var ok = O_StickyViewModel.overwriteWithAnnotations(stickyWin.imageUrl, layer)
if (ok)
refreshAfterImageChanged()
}
function rotateCurrent(degrees) {
if (O_StickyViewModel.rotateImage(stickyWin.imageUrl, degrees))
refreshAfterImageChanged()
}
function mirrorCurrent() {
if (O_StickyViewModel.mirrorImage(stickyWin.imageUrl, true, false))
refreshAfterImageChanged()
}
function saveMergedTo(pathUrl) {
var layer = stickyPaintBoard.renderToImage()
O_StickyViewModel.saveMergedImage(stickyWin.imageUrl, layer, pathUrl)
}
function completeEditsAndCopy() {
var layer = stickyPaintBoard.renderToImage()
if (layer && layer.width > 0 && layer.height > 0)
overwriteWithEdits()
O_StickyViewModel.copyImageToClipboard(stickyWin.imageUrl)
stickyTool.activeTool = ""
stickyWin.activeTool = ""
stickyWin.toolbarVisible = false
function syncInitialPosition() {
O_StickyViewModel.positionStickyWindow(stickyWin,
Qt.rect(imgRect.x - shadowPadding,
imgRect.y - shadowPadding,
imgRect.width + shadowPadding * 2,
imgRect.height + shadowPadding * 2))
}
function cleanup() {
if (cleanedUp)
return
cleanedUp = true
stickyWin.visible = false
contentActive = false
stickyWin.releaseResources()
chatPanel.close()
O_StickyViewModel.releaseImage(imageUrl)
imageUrl = ""
}
function syncPaintBoardBackground() {
if (!imageUrl) {
paintBoard.reset()
return
}
var bg = O_StickyViewModel.getImageByUrl(imageUrl)
paintBoard.reset()
paintBoard.setBackgroundImg(bg)
}
function openChatPanel() {
var targetScreen = stickyWin.screen
if (!targetScreen || !targetScreen.availableGeometry) {
chatPanel.open(stickyWin.x, stickyWin.y + stickyWin.height + 6, targetScreen)
return
}
var available = targetScreen.availableGeometry
var panelX = stickyWin.x
var panelY = stickyWin.y + stickyWin.height + 6
if (panelY + chatPanel.win.height > available.y + available.height)
panelY = stickyWin.y - chatPanel.win.height - 6
panelX = Math.max(available.x, Math.min(panelX, available.x + available.width - chatPanel.win.width))
panelY = Math.max(available.y, Math.min(panelY, available.y + available.height - chatPanel.win.height))
chatPanel.open(Math.round(panelX), Math.round(panelY), targetScreen)
}
Component.onCompleted: {
updateDpiScale()
updateImageSize()
syncPaintBoardBackground()
syncInitialPosition()
updateImageMetricsFromStore()
updateImageLogicalSize()
applyNativeSize()
stickyWin.raise()
stickyWin.requestActivate()
}
onImageUrlChanged: {
imageRevision = 0
sourceImageSize = Qt.size(1, 1)
imageLogicalWidth = 1
imageLogicalHeight = 1
resetDisplaySize()
refreshAfterImageChanged()
updateImageSize()
syncPaintBoardBackground()
}
onActiveToolChanged: {
if (activeTool === "mosaic")
updateImageMetricsFromStore()
}
onScreenChanged: {
updateDpiScale()
updateImageLogicalSize()
}
onImgRectChanged: syncInitialPosition()
Component.onDestruction: cleanup()
function onClosing(close) {
close.accepted = true
chatPanel.close()
cleanup()
Qt.callLater(function() { stickyWin.destroy() })
}
Rectangle {
id: imgContainer
visible: stickyWin.contentActive
z: 10
x: 0
y: 0
width: stickyWin.displayW
height: stickyWin.displayH
radius: 2
clip: true
color: "transparent"
Item {
id: visualStage
anchors.centerIn: parent
width: stickyWin.imageLogicalWidth
height: stickyWin.imageLogicalHeight
scale: stickyWin.zoomFactor
Image {
id: stickyImage
anchors.fill: parent
source: displaySourceUrl()
fillMode: Image.Stretch
opacity: stickyWin.imgOpacity
cache: false
onStatusChanged: {
if (status === Image.Ready)
stickyWin.updateImageLogicalSize()
if (status === Image.Ready && stickyWin.activeTool === "mosaic" && !stickyPaintBoard.hasBackgroundImg())
stickyWin.syncPaintboardBackgroundFromDisplay()
}
}
PaintBoard {
id: stickyPaintBoard
anchors.fill: parent
z: 20
visible: stickyWin.contentActive
enabled: stickyWin.activeTool !== ""
shapeType: stickyWin.stickyShapeType
penColor: stickyMarkTool.currentColor
penSize: stickyMarkTool.currentSize
mosaicBlurLevel: stickyMarkTool.mosaicBlurLevel
}
}
RectangularGlow {
id: stickyShadow
x: stickyWin.contentX
y: stickyWin.contentY
width: stickyWin.contentWidth
height: stickyWin.contentHeight
glowRadius: 10
spread: 0.08
color: stickyWin.shadowColor
cornerRadius: 2
opacity: 0.45
cached: true
}
MultiEffect {
visible: stickyWin.contentActive
PaintBoard {
id: paintBoard
x: stickyWin.contentX
y: stickyWin.contentY
width: stickyWin.contentWidth
height: stickyWin.contentHeight
z: 5
source: stickyImage
anchors.fill: imgContainer
shadowEnabled: true
shadowColor: "#55000000"
shadowBlur: stickyWin.shadowRadius / 20.0
shadowVerticalOffset: stickyWin.shadowOffset
shadowHorizontalOffset: 0
opacity: stickyWin.imgOpacity
}
TZLoadingSpinner {
anchors.centerIn: imgContainer
width: stickyWin.displayW
height: stickyWin.displayH
visible: O_AIViewModel.isLoading && stickyWin.contentActive
}
TZMarkToolInfo {
id: stickyMarkTool
z: 32
visible: stickyWin.toolbarVisible && stickyWin.activeTool !== ""
toolName: stickyWin.activeTool
showColorRow: stickyWin.activeTool !== "mosaic"
x: Math.max(8, toolbarBlockX)
y: stickyTool.y - height - 6
visible: true
enabled: toolBar.activeTool !== ""
shapeType: toolBar.shapeType
penColor: markToolInfo.currentColor
penSize: markToolInfo.currentSize
}
TZTool {
id: stickyTool
z: 30
id: toolBar
visible: stickyWin.toolbarVisible
width: implicitWidth
height: stickyWin.toolbarHeight + 4
x: stickyWin.toolbarBlockX
y: stickyWin.toolbarTopY
x: Math.max(0, Math.floor((stickyWin.width - width) / 2))
y: stickyWin.contentY + stickyWin.contentHeight + stickyWin.toolbarGap
showScreenshotButton: false
showCancelButton: false
showSaveButton: false
@@ -321,76 +177,31 @@ Window {
showHighlightButton: false
showTextButton: false
showNumberButton: false
showRectButton: false
showCircleButton: false
showArrowButton: false
showMosaicButton: false
showPencilButton: true
showUndoButton: true
onActiveToolChanged: {
stickyWin.activeTool = activeTool
if (activeTool === "mosaic" && !stickyPaintBoard.hasBackgroundImg()) {
Qt.callLater(function() {
var retryImg = O_StickyViewModel.getImageByUrl(stickyWin.imageUrl)
if (retryImg && retryImg.width > 0 && retryImg.height > 0)
stickyPaintBoard.setBackgroundImg(retryImg)
if (!stickyPaintBoard.hasBackgroundImg())
stickyWin.syncPaintboardBackgroundFromDisplay()
})
}
}
onSignalOcr: {
var img = O_StickyViewModel.getImageByUrl(stickyWin.imageUrl)
O_OcrVM.recognize(img)
}
onSignalUndo: stickyPaintBoard.undo()
onSignalOcr: {}
onSignalUndo: paintBoard.undo()
}
TZStickyExtraToolBar {
id: stickyExtraBar
z: 30
visible: stickyWin.toolbarVisible
height: stickyWin.toolbarHeight
x: stickyWin.toolbarBlockX + stickyTool.width + 6
y: stickyTool.y + 2
zoomFactor: stickyWin.zoomFactor
onZoomOut: setZoomValue(stickyWin.zoomFactor - 0.1)
onZoomIn: setZoomValue(stickyWin.zoomFactor + 0.1)
onRotate90: rotateCurrent(90)
onMirror: mirrorCurrent()
onResetSize: resetDisplaySize()
onDoneAndCopy: completeEditsAndCopy()
onHideToolbar: {
stickyTool.activeTool = ""
stickyWin.activeTool = ""
stickyWin.toolbarVisible = false
}
onHoverEntered: function(text, targetItem) { stickyWin.showToolbarTip(text, targetItem) }
onHoverExited: function(text) { stickyWin.hideToolbarTip(text) }
TZMarkToolInfo {
id: markToolInfo
visible: toolBar.visible && toolBar.markToolInfoVisible && toolBar.activeTool === "pencil"
z: 6
x: Math.max(stickyWin.shadowPadding, Math.min(stickyWin.width - width - stickyWin.shadowPadding, toolBar.x))
y: Math.max(stickyWin.shadowPadding, toolBar.y - height - stickyWin.markToolGap)
toolName: toolBar.activeTool
showColorRow: true
}
Rectangle {
id: stickyToolbarTip
parent: stickyWin.contentItem
visible: stickyWin.toolbarVisible && stickyWin.toolbarHoverTipText.length > 0
z: 40
width: Math.min(stickyWin.width - 8, Math.max(64, stickyToolbarTipText.implicitWidth + 16))
height: 24
radius: 6
color: "#0F172A"
opacity: 0.92
x: Math.max(4, Math.min(stickyWin.width - width - 4, stickyExtraBar.x + stickyWin.toolbarHoverTipCenterX - width / 2))
y: Math.min(stickyWin.height - height - 4, stickyExtraBar.y + stickyExtraBar.height + stickyWin.toolbarTipGap)
Text {
id: stickyToolbarTipText
anchors.centerIn: parent
text: stickyWin.toolbarHoverTipText
color: "#FFFFFF"
font.pixelSize: 11
}
}
readonly property int chatPanelGap: 6
TZChat {
id: chatPanel
imageUrl: stickyWin.imageUrl
onSignalSendPrompt: function(text) {
O_AIViewModel.sendPrompt(text, stickyWin.imageUrl)
}
@@ -398,123 +209,109 @@ Window {
Connections {
target: O_AIViewModel
function onSignalRequestComplete(oldImageUrl, newImageUrl) {
if (oldImageUrl !== stickyWin.imageUrl)
return
var old = oldImageUrl
stickyWin.imageUrl = newImageUrl
syncPaintBoardBackground()
Qt.callLater(function() { O_StickyViewModel.releaseImage(old) })
}
function onSignalRequestFailed(errorMsg) {
console.warn("[TZStickyWindow] AI request failed: " + errorMsg)
}
}
function openChatPanel() {
var imgLeft = stickyWin.x
var imgTop = stickyWin.y
var imgBottom = imgTop + stickyWin.displayH
Platform.Menu {
id: contextMenu
var screenH = Screen.desktopAvailableHeight
var screenW = Screen.desktopAvailableWidth
Platform.MenuItem {
text: qsTr("复制")
onTriggered: O_StickyViewModel.copyImageToClipboard(stickyWin.imageUrl)
}
var cx = imgLeft
if (cx + chatPanel.win.width > screenW)
cx = imgLeft + stickyWin.displayW - chatPanel.win.width
Platform.MenuItem {
text: qsTr("AI 编辑")
onTriggered: stickyWin.openChatPanel()
}
var cy
if (imgBottom + chatPanelGap + chatPanel.win.height <= screenH)
cy = imgBottom + chatPanelGap
else
cy = imgTop - chatPanelGap - chatPanel.win.height
chatPanel.open(Math.max(0, cx), Math.max(0, cy))
}
TZStickyContextMenu {
id: stickyContextMenu
menuPrefs: menuPrefs
imageUrl: stickyWin.imageUrl
imgOpacity: stickyWin.imgOpacity
zoomFactor: stickyWin.zoomFactor
toolbarVisible: stickyWin.toolbarVisible
saveFolder: O_ImageSaver.savePath
overwriteWithEdits: function() { stickyWin.overwriteWithEdits() }
copyImageToClipboard: function(url) { O_StickyViewModel.copyImageToClipboard(url) }
openChatPanel: function() { stickyWin.openChatPanel() }
hideOrToggleToolbar: function() {
stickyWin.toolbarVisible = !stickyWin.toolbarVisible
if (!stickyWin.toolbarVisible) {
stickyTool.activeTool = ""
stickyWin.activeTool = ""
Platform.MenuItem {
text: stickyWin.toolbarVisible ? qsTr("隐藏工具栏") : qsTr("显示工具栏")
onTriggered: {
stickyWin.toolbarVisible = !stickyWin.toolbarVisible
updateWindowSize()
}
}
setOpacityValue: function(value) { stickyWin.setOpacityValue(value) }
setZoomValue: function(value) { stickyWin.setZoomValue(value) }
rotateCurrent: function(degrees) { stickyWin.rotateCurrent(degrees) }
mirrorCurrent: function() { stickyWin.mirrorCurrent() }
resetDisplaySize: function() { stickyWin.resetDisplaySize() }
resetWindowPosition: function() { stickyWin.syncInitialPosition() }
saveOriginalImage: function(url, pathUrl) { O_StickyViewModel.saveImage(url, pathUrl) }
saveMergedTo: function(pathUrl) { stickyWin.saveMergedTo(pathUrl) }
closeWindow: function() { stickyWin.close() }
Platform.MenuItem {
text: qsTr("恢复原图大小")
onTriggered: stickyWin.resetDisplaySize()
}
Platform.MenuSeparator {}
Platform.MenuItem {
text: qsTr("保存原图")
onTriggered: saveDialog.open()
}
Platform.MenuItem {
text: qsTr("关闭")
onTriggered: stickyWin.close()
}
}
Platform.FileDialog {
id: saveDialog
title: qsTr("选择保存位置")
folder: O_ImageSaver.savePath
fileMode: Platform.FileDialog.SaveFile
nameFilters: ["PNG (*.png)", "JPG (*.jpg)", "BMP (*.bmp)"]
onAccepted: O_StickyViewModel.saveImage(stickyWin.imageUrl, saveDialog.file)
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
enabled: stickyWin.activeTool === "" && !stickyWin.contextMenuOpen
property point dragPos: Qt.point(0, 0)
property bool leftDragActive: false
z: 0
function inImageArea(mx, my) {
return mx >= 0
&& mx <= stickyWin.displayW
&& my >= 0
&& my <= stickyWin.displayH
}
id: dragArea
x: stickyWin.contentX
y: stickyWin.contentY
width: stickyWin.contentWidth
height: stickyWin.contentHeight
enabled: toolBar.activeTool === ""
acceptedButtons: Qt.LeftButton
property point pressOffset: Qt.point(0, 0)
onPressed: function(mouse) {
if (mouse.button === Qt.LeftButton) {
dragPos = Qt.point(mouseX, mouseY)
leftDragActive = true
} else {
leftDragActive = false
}
pressOffset = Qt.point(mouse.x, mouse.y)
}
onPositionChanged: function(mouse) {
if (pressed && leftDragActive && (mouse.buttons & Qt.LeftButton) && stickyWin.activeTool === "" && !stickyWin.contextMenuOpen) {
stickyWin.x += mouseX - dragPos.x
stickyWin.y += mouseY - dragPos.y
}
}
onReleased: function(mouse) {
if (!(mouse.buttons & Qt.LeftButton))
leftDragActive = false
}
onDoubleClicked: function(mouse) {
if (mouse.button === Qt.LeftButton && inImageArea(mouseX, mouseY))
resetDisplaySize()
}
onClicked: function(mouse) {
if (mouse.button === Qt.RightButton) {
leftDragActive = false
stickyContextMenu.open()
if (pressed && (mouse.buttons & Qt.LeftButton)) {
stickyWin.x += mouse.x - pressOffset.x
stickyWin.y += mouse.y - pressOffset.y
}
}
onWheel: function(wheel) {
if (wheel.angleDelta.y > 0)
setZoomValue(stickyWin.zoomFactor + 0.1)
stickyWin.setZoomFactor(stickyWin.zoomFactor + 0.1)
else if (wheel.angleDelta.y < 0)
setZoomValue(stickyWin.zoomFactor - 0.1)
stickyWin.setZoomFactor(stickyWin.zoomFactor - 0.1)
wheel.accepted = true
}
}
MouseArea {
x: stickyWin.contentX
y: stickyWin.contentY
width: stickyWin.contentWidth
height: stickyWin.contentHeight + (stickyWin.toolbarVisible ? (stickyWin.toolbarHeight + stickyWin.toolbarGap + stickyWin.toolbarTipReserve) : 0)
acceptedButtons: Qt.RightButton
onClicked: function(mouse) {
if (mouse.button === Qt.RightButton)
contextMenu.open()
}
}
}
+10
View File
@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 12h2"/>
<path d="M7 12h4"/>
<path d="M15 12h6"/>
<path d="M3 7h6"/>
<path d="M13 7h2"/>
<path d="M19 7h2"/>
<path d="M3 17h10"/>
<path d="M17 17h4"/>
</svg>

After

Width:  |  Height:  |  Size: 367 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-smile-plus-icon lucide-smile-plus"><path d="M22 11v1a10 10 0 1 1-9-10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" x2="9.01" y1="9" y2="9"/><line x1="15" x2="15.01" y1="9" y2="9"/><path d="M16 5h6"/><path d="M19 2v6"/></svg>

Before

Width:  |  Height:  |  Size: 431 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file-icon lucide-file"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/></svg>

Before

Width:  |  Height:  |  Size: 388 B

+81
View File
@@ -0,0 +1,81 @@
#stickyToolbar {
background-color: #FFFFFF;
border: 1px solid rgba(96, 165, 250, 170);
border-radius: 10px;
}
#stickyToolbar QToolButton {
background-color: transparent;
color: #0F172A;
border: 1px solid transparent;
border-radius: 7px;
padding: 0px;
margin: 0px;
qproperty-iconSize: 18px 18px;
}
#stickyToolbar QToolButton:hover {
background-color: #F1F5F9;
border-color: transparent;
}
#stickyToolbar QToolButton:pressed {
background-color: #EFF6FF;
border-color: #3B82F6;
}
#stickyToolbar QToolButton:checked {
background-color: #EFF6FF;
border-color: #3B82F6;
}
#stickyToolOptions {
background-color: #FFFFFF;
border: 1px solid #E2E8F0;
border-radius: 10px;
}
#stickyToolOptions QToolButton {
background-color: transparent;
border: 1px solid #E2E8F0;
border-radius: 6px;
color: #334155;
}
#stickyToolOptions QToolButton:checked {
background-color: #EFF6FF;
border-color: #3B82F6;
color: #3B82F6;
}
#stickyToolOptions QToolButton:hover {
background-color: #F1F5F9;
}
#stickyToolOptions QLineEdit,
#stickyToolOptions QSpinBox {
background-color: #F8FAFC;
border: 1px solid #E2E8F0;
border-radius: 6px;
padding: 4px 6px;
color: #0F172A;
}
#stickyToolOptions QCheckBox {
color: #334155;
}
#stickyInlineTextEditor {
background-color: #FFFFFF;
border: 1px solid #3B82F6;
border-radius: 6px;
padding: 4px 6px;
color: #0F172A;
}
#stickyNumberLabel {
background-color: #F8FAFC;
border: 1px solid #E2E8F0;
border-radius: 6px;
color: #0F172A;
}
+5 -3
View File
@@ -29,9 +29,10 @@
int main(int argc, char *argv[])
{
// 强制让窗口跟随系统DPI,不使用虚拟像素
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(
Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
// QGuiApplication::setHighDpiScaleFactorRoundingPolicy(
// Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor);
QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);
QApplication app(argc, argv);
// Single-instance activation: second launch notifies the first one and exits.
@@ -55,6 +56,7 @@ int main(int argc, char *argv[])
ScreenshotViewModel screenshotViewModel(desktopSnapshot, stickyImageStore);
StickyViewModel stickyViewModel(stickyImageStore);
AIViewModel aiViewModel(appSettings, stickyImageStore);
stickyViewModel.setAiViewModel(&aiViewModel);
StorageViewModel storageViewModel(appSettings);
LanguageManager languageManager(appSettings);
GifRecordViewModel gifRecordViewModel(appSettings);
+160 -2
View File
@@ -7,6 +7,8 @@
#include <QPainter>
#include <QPixmap>
#include <QScreen>
#include <algorithm>
#include <limits>
#ifdef Q_OS_WIN
#define WIN32_LEAN_AND_MEAN
@@ -15,6 +17,18 @@
namespace {
struct ScreenSortLess
{
template <typename T>
bool operator()(const T &lhs, const T &rhs) const
{
if (lhs.left() != rhs.left()) {
return lhs.left() < rhs.left();
}
return lhs.top() < rhs.top();
}
};
QRect desktopGeometry()
{
#ifdef Q_OS_WIN
@@ -78,6 +92,33 @@ QImage captureVirtualDesktopImage(const QRect& geometry)
ReleaseDC(nullptr, screenDC);
return image;
}
QVector<QRect> enumerateMonitorRects()
{
struct MonitorRects {
QVector<QRect> rects;
} monitors;
EnumDisplayMonitors(nullptr,
nullptr,
[](HMONITOR monitor, HDC, LPRECT, LPARAM data) -> BOOL {
auto *rects = reinterpret_cast<MonitorRects*>(data);
MONITORINFOEXW info{};
info.cbSize = sizeof(info);
if (GetMonitorInfoW(monitor, &info)) {
const RECT &rc = info.rcMonitor;
rects->rects.append(QRect(rc.left,
rc.top,
rc.right - rc.left,
rc.bottom - rc.top));
}
return TRUE;
},
reinterpret_cast<LPARAM>(&monitors));
std::sort(monitors.rects.begin(), monitors.rects.end(), ScreenSortLess{});
return monitors.rects;
}
#endif
}
@@ -123,6 +164,7 @@ void DesktopSnapshot::grab()
#endif
m_virtualGeometry = geometry;
buildScreenEntries();
buildWindowList();
}
@@ -131,6 +173,102 @@ void DesktopSnapshot::release()
m_snapshot = QPixmap();
m_virtualGeometry = QRect();
m_windowRects.clear();
m_screenEntries.clear();
}
void DesktopSnapshot::buildScreenEntries()
{
m_screenEntries.clear();
QList<QScreen*> screens = QGuiApplication::screens();
if (screens.isEmpty()) {
return;
}
#ifdef Q_OS_WIN
const QVector<QRect> monitorRects = enumerateMonitorRects();
std::sort(screens.begin(), screens.end(), [](QScreen *lhs, QScreen *rhs) {
if (!lhs || !rhs) {
return lhs < rhs;
}
return ScreenSortLess{}(lhs->geometry(), rhs->geometry());
});
const int pairCount = qMin(screens.size(), monitorRects.size());
for (int i = 0; i < pairCount; ++i) {
QScreen *screen = screens.at(i);
if (!screen) {
continue;
}
ScreenEntry entry;
entry.logicalGeometry = screen->geometry();
entry.physicalRect = monitorRects.at(i);
entry.dpr = screen->devicePixelRatio();
if (entry.dpr <= 0.0) {
entry.dpr = 1.0;
}
m_screenEntries.append(entry);
}
for (int i = pairCount; i < screens.size(); ++i) {
QScreen *screen = screens.at(i);
if (!screen) {
continue;
}
ScreenEntry entry;
entry.logicalGeometry = screen->geometry();
entry.dpr = screen->devicePixelRatio();
if (entry.dpr <= 0.0) {
entry.dpr = 1.0;
}
entry.physicalRect = QRect(qRound(entry.logicalGeometry.x() * entry.dpr),
qRound(entry.logicalGeometry.y() * entry.dpr),
qRound(entry.logicalGeometry.width() * entry.dpr),
qRound(entry.logicalGeometry.height() * entry.dpr));
m_screenEntries.append(entry);
}
#else
for (QScreen *screen : screens) {
if (!screen) {
continue;
}
ScreenEntry entry;
entry.logicalGeometry = screen->geometry();
entry.physicalRect = entry.logicalGeometry;
entry.dpr = screen->devicePixelRatio();
if (entry.dpr <= 0.0) {
entry.dpr = 1.0;
}
m_screenEntries.append(entry);
}
#endif
}
const DesktopSnapshot::ScreenEntry *DesktopSnapshot::screenEntryForPoint(const QPoint &logicalPoint) const
{
for (const ScreenEntry &entry : m_screenEntries) {
if (entry.logicalGeometry.contains(logicalPoint)) {
return &entry;
}
}
if (m_screenEntries.isEmpty()) {
return nullptr;
}
const ScreenEntry *nearest = &m_screenEntries.first();
qreal bestDistance = (std::numeric_limits<qreal>::max)();
for (const ScreenEntry &entry : m_screenEntries) {
const QPoint center = entry.logicalGeometry.center();
const qreal dx = logicalPoint.x() - center.x();
const qreal dy = logicalPoint.y() - center.y();
const qreal distance = dx * dx + dy * dy;
if (distance < bestDistance) {
bestDistance = distance;
nearest = &entry;
}
}
return nearest;
}
QPoint DesktopSnapshot::mapLogicalPointToPhysical(const QPoint &logicalPoint) const
@@ -138,7 +276,16 @@ QPoint DesktopSnapshot::mapLogicalPointToPhysical(const QPoint &logicalPoint) co
if (m_virtualGeometry.isEmpty()) {
return {};
}
return logicalPoint - m_virtualGeometry.topLeft();
const ScreenEntry *entry = screenEntryForPoint(logicalPoint);
if (!entry) {
return {};
}
const QPoint localPoint = logicalPoint - entry->logicalGeometry.topLeft();
const QPoint physicalPoint(entry->physicalRect.x() + qRound(localPoint.x() * entry->dpr),
entry->physicalRect.y() + qRound(localPoint.y() * entry->dpr));
return physicalPoint - m_virtualGeometry.topLeft();
}
QRect DesktopSnapshot::mapLogicalRectToPhysical(const QRect &logicalRect) const
@@ -147,7 +294,18 @@ QRect DesktopSnapshot::mapLogicalRectToPhysical(const QRect &logicalRect) const
if (normalized.isEmpty() || m_virtualGeometry.isEmpty()) {
return {};
}
QRect translated = normalized.translated(-m_virtualGeometry.topLeft());
const ScreenEntry *entry = screenEntryForPoint(normalized.center());
if (!entry) {
return {};
}
const QPoint localTopLeft = normalized.topLeft() - entry->logicalGeometry.topLeft();
const QPoint absolutePhysicalTopLeft(entry->physicalRect.x() + qRound(localTopLeft.x() * entry->dpr),
entry->physicalRect.y() + qRound(localTopLeft.y() * entry->dpr));
QRect translated(absolutePhysicalTopLeft - m_virtualGeometry.topLeft(),
QSize(qMax(1, qRound(normalized.width() * entry->dpr)),
qMax(1, qRound(normalized.height() * entry->dpr))));
return translated.intersected(m_snapshot.rect());
}
+1
View File
@@ -31,6 +31,7 @@ private:
qreal dpr = 1.0;
};
void buildScreenEntries();
void buildWindowList();
const ScreenEntry* screenEntryForPoint(const QPoint &logicalPoint) const;
+14 -8
View File
@@ -1,15 +1,19 @@
#include "paint_board.h"
#include "paint_board.h"
#include "shape/mosaic_shape.h"
#include <QDebug>
PaintBoard::PaintBoard(QQuickItem *parent)
: QQuickPaintedItem(parent)
{
setRenderTarget(QQuickPaintedItem::FramebufferObject);
setAcceptedMouseButtons(Qt::LeftButton);
}
void PaintBoard::setBackgroundImg(QImage img)
{
// if (!img.isNull()) {
// img.setDevicePixelRatio(1.0);
// }
m_background_img = img;
update();
}
@@ -125,7 +129,8 @@ QImage PaintBoard::renderToImage() const
if (m_shapes.isEmpty())
return QImage();
QImage img(qRound(width()), qRound(height()), QImage::Format_ARGB32);
QImage img(qRound(width() ), qRound(height()), QImage::Format_ARGB32);
img.setDevicePixelRatio(1.0);
img.fill(Qt::transparent);
QPainter painter(&img);
@@ -140,13 +145,16 @@ QImage PaintBoard::renderToImage() const
void PaintBoard::paint(QPainter *painter)
{
// painter->save();
// painter->scale(1.0 / m_canvasScale, 1.0 / m_canvasScale);
drawBkImg(painter, m_background_img);
drawShape(painter);
// painter->restore();
}
void PaintBoard::mousePressEvent(QMouseEvent *event)
{
m_startPoint = event->position().toPoint();
m_startPoint = (event->position()).toPoint();
m_drawing = true;
if (m_currentType == TEXT) {
@@ -206,7 +214,7 @@ void PaintBoard::mouseMoveEvent(QMouseEvent *event)
if (!m_drawing || !m_currentShape)
return;
m_currentShape->setEndPoint(event->position().toPoint());
m_currentShape->setEndPoint((event->position()).toPoint());
update();
}
@@ -215,7 +223,7 @@ void PaintBoard::mouseReleaseEvent(QMouseEvent *event)
if (!m_drawing || !m_currentShape)
return;
m_currentShape->setEndPoint(event->position().toPoint());
m_currentShape->setEndPoint((event->position()).toPoint());
m_shapes.append(m_currentShape);
m_currentShape = nullptr;
m_drawing = false;
@@ -232,9 +240,7 @@ bool PaintBoard::drawBkImg(QPainter *painter, QImage img)
if (!painter || img.isNull())
return false;
QRectF drawRect = img.rect();
drawRect.moveCenter(boundingRect().center());
painter->drawImage(drawRect, img);
painter->drawImage(QRectF(0, 0, width() , height() ), img);
return true;
}
+14 -15
View File
@@ -12,37 +12,36 @@ void ArrowShape::draw(QPainter *painter)
if (m_startPoint == m_endPoint) return;
painter->save();
const QPointF startPoint(m_startPoint);
const QPointF endPoint(m_endPoint);
QPen pen(m_color, m_size);
pen.setCapStyle(Qt::RoundCap);
pen.setJoinStyle(Qt::RoundJoin);
painter->setPen(pen);
painter->setRenderHint(QPainter::Antialiasing);
// 绘制箭杆
painter->drawLine(m_startPoint, m_endPoint);
painter->drawLine(startPoint, endPoint);
// 计算箭头方向角
const double angle = std::atan2(
m_endPoint.y() - m_startPoint.y(),
m_endPoint.x() - m_startPoint.x()
endPoint.y() - startPoint.y(),
endPoint.x() - startPoint.x()
);
// 计算箭头两侧的点
QPoint arrowP1(
m_endPoint.x() - static_cast<int>(std::cos(angle + ARROW_HEAD_ANGLE) * ARROW_HEAD_LENGTH),
m_endPoint.y() - static_cast<int>(std::sin(angle + ARROW_HEAD_ANGLE) * ARROW_HEAD_LENGTH)
const qreal headLength = ARROW_HEAD_LENGTH;
QPointF arrowP1(
endPoint.x() - std::cos(angle + ARROW_HEAD_ANGLE) * headLength,
endPoint.y() - std::sin(angle + ARROW_HEAD_ANGLE) * headLength
);
QPoint arrowP2(
m_endPoint.x() - static_cast<int>(std::cos(angle - ARROW_HEAD_ANGLE) * ARROW_HEAD_LENGTH),
m_endPoint.y() - static_cast<int>(std::sin(angle - ARROW_HEAD_ANGLE) * ARROW_HEAD_LENGTH)
QPointF arrowP2(
endPoint.x() - std::cos(angle - ARROW_HEAD_ANGLE) * headLength,
endPoint.y() - std::sin(angle - ARROW_HEAD_ANGLE) * headLength
);
// 绘制实心箭头头部
QPolygon arrowHead;
arrowHead << m_endPoint << arrowP1 << arrowP2;
QPolygonF arrowHead;
arrowHead << endPoint << arrowP1 << arrowP2;
painter->setBrush(QBrush(m_color));
painter->drawPolygon(arrowHead);
painter->restore();
}
+65
View File
@@ -0,0 +1,65 @@
#include "blur_shape.h"
#include <QPainter>
#include <QPainterPath>
BlurShape::BlurShape(const QPoint &startPoint, int brushRadius, int blurRadius)
: Shape(Qt::transparent, blurRadius)
, m_brushRadius(qMax(4, brushRadius))
, m_blurRadius(qMax(2, blurRadius))
{
m_points.append(startPoint);
}
void BlurShape::setEndPoint(const QPoint &point)
{
if (!m_points.isEmpty() && m_points.last() == point) {
return;
}
m_points.append(point);
}
void BlurShape::draw(QPainter *painter)
{
if (!painter || m_blurredSnapshot.isNull() || m_points.isEmpty()) {
return;
}
painter->save();
painter->setRenderHint(QPainter::Antialiasing, true);
for (const QPoint &point : m_points) {
QPainterPath path;
path.addEllipse(QPointF(point), m_brushRadius, m_brushRadius);
painter->save();
painter->setClipPath(path);
painter->drawImage(QPoint(0, 0), m_blurredSnapshot);
painter->restore();
}
painter->restore();
}
void BlurShape::setCanvasSnapshot(const QImage &snapshot)
{
if (snapshot.isNull()) {
return;
}
m_canvasSnapshot = snapshot.convertToFormat(QImage::Format_ARGB32);
m_blurredSnapshot = makeBlurredImage(m_canvasSnapshot);
}
QImage BlurShape::makeBlurredImage(const QImage &source) const
{
if (source.isNull()) {
return {};
}
const int downscale = qMax(1, m_blurRadius);
const QSize smallSize(qMax(1, source.width() / downscale),
qMax(1, source.height() / downscale));
const QImage small = source.scaled(smallSize,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
return small.scaled(source.size(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef BLUR_SHAPE_H
#define BLUR_SHAPE_H
#include "shape.h"
#include <QImage>
#include <QVector>
class BlurShape : public Shape
{
public:
BlurShape(const QPoint& startPoint,
int brushRadius = 20,
int blurRadius = 8);
void setEndPoint(const QPoint& point) override;
void draw(QPainter* painter) override;
void setCanvasSnapshot(const QImage& snapshot);
private:
QImage makeBlurredImage(const QImage& source) const;
QVector<QPoint> m_points;
QImage m_canvasSnapshot;
QImage m_blurredSnapshot;
int m_brushRadius;
int m_blurRadius;
};
#endif // BLUR_SHAPE_H
-2
View File
@@ -12,8 +12,6 @@ void EllipseShape::draw(QPainter *painter)
painter->setPen(pen);
painter->setBrush(Qt::NoBrush);
painter->setRenderHint(QPainter::Antialiasing);
// 用两点构造外接矩形来绘制椭圆,normalized() 处理反向拖动
painter->drawEllipse(QRect(m_startPoint, m_endPoint).normalized());
painter->restore();
}
+4 -1
View File
@@ -57,7 +57,10 @@ void MosaicShape::draw(QPainter* painter)
const QPoint& blockOrigin = it.key();
const QColor& color = it.value();
QRect blockRect(blockOrigin.x(), blockOrigin.y(), m_blockSize, m_blockSize);
QRectF blockRect(blockOrigin.x(),
blockOrigin.y(),
m_blockSize,
m_blockSize);
// 无需裁剪到背景图范围,因为我们不再依赖背景图
// 但为了保证安全,可以检查坐标是否为正
+4 -2
View File
@@ -25,9 +25,11 @@ void NumberShape::draw(QPainter* painter)
painter->save();
painter->setRenderHint(QPainter::Antialiasing, true);
painter->setRenderHint(QPainter::TextAntialiasing, true);
const int r = qMax(10, m_size * 2 + 6);
const QRect circleRect(m_center.x() - r, m_center.y() - r, r * 2, r * 2);
const QRect circleRect(m_center.x() - r,
m_center.y() - r,
r * 2,
r * 2);
painter->setPen(QPen(Qt::white, qMax(2, m_size / 2)));
painter->setBrush(m_color);
+1 -4
View File
@@ -1,4 +1,4 @@
#include "pen_shape.h"
#include "pen_shape.h"
PenShape::PenShape(const QPoint &startPoint, const QColor &color, int size)
: Shape(color, size)
@@ -25,14 +25,11 @@ void PenShape::draw(QPainter *painter)
return;
painter->save();
QPen pen(m_color, m_size);
pen.setCapStyle(Qt::RoundCap); // 端点圆滑
pen.setJoinStyle(Qt::RoundJoin); // 转折点圆滑
painter->setPen(pen);
painter->setRenderHint(QPainter::Antialiasing);
// 将所有点连成折线
painter->drawPolyline(QPolygon(m_points));
painter->restore();
-2
View File
@@ -12,8 +12,6 @@ void RectShape::draw(QPainter *painter)
painter->setPen(pen);
painter->setBrush(Qt::NoBrush);
painter->setRenderHint(QPainter::Antialiasing);
// normalized() 处理从右下往左上拖动的情况
painter->drawRect(QRect(m_startPoint, m_endPoint).normalized());
painter->restore();
}
+2 -1
View File
@@ -10,7 +10,8 @@ enum Shapeype
MOSAIC,
TEXT,
NUMBER,
HIGHLIGHT
HIGHLIGHT,
BLUR
};
#endif // SHAPETYPE_H
-1
View File
@@ -27,7 +27,6 @@ void TextShape::draw(QPainter* painter)
painter->save();
painter->setRenderHint(QPainter::Antialiasing, true);
painter->setRenderHint(QPainter::TextAntialiasing, true);
QFont f = painter->font();
f.setPixelSize(qMax(12, m_size * 4));
painter->setFont(f);
+50 -10
View File
@@ -10,13 +10,32 @@
#include <QScreen>
#include <QWindow>
#ifdef Q_OS_WIN
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <shellscalingapi.h>
#endif
namespace {
qreal captureDprForRect(const QRect& rect)
{
#ifdef Q_OS_WIN
if (QScreen* screen = QGuiApplication::screenAt(rect.center())) {
return screen->devicePixelRatio();
const POINT centerPoint {
rect.center().x(),
rect.center().y()
};
if (HMONITOR monitor = MonitorFromPoint(centerPoint, MONITOR_DEFAULTTONEAREST)) {
using GetDpiForMonitorFn = HRESULT (WINAPI *)(HMONITOR, MONITOR_DPI_TYPE, UINT *, UINT *);
static const auto getDpiForMonitor = reinterpret_cast<GetDpiForMonitorFn>(
GetProcAddress(LoadLibraryW(L"Shcore.dll"), "GetDpiForMonitor"));
if (getDpiForMonitor) {
UINT dpiX = 0;
UINT dpiY = 0;
if (SUCCEEDED(getDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY)) && dpiX > 0) {
return qreal(dpiX) / 96.0;
}
}
}
#endif
if (QScreen* screen = QGuiApplication::primaryScreen()) {
@@ -25,6 +44,20 @@ qreal captureDprForRect(const QRect& rect)
return 1.0;
}
qreal effectiveCaptureDpr(QQuickItem *paintBoard, const QRect &rect)
{
if (!rect.isEmpty()) {
return captureDprForRect(rect);
}
if (paintBoard && paintBoard->window()) {
return paintBoard->window()->devicePixelRatio();
}
if (QScreen *screen = QGuiApplication::primaryScreen()) {
return screen->devicePixelRatio();
}
return 1.0;
}
QRect captureRectInSnapshot(const DesktopSnapshot& snapshot, const QRect& rect, qreal dpr)
{
if (snapshot.isNull()) {
@@ -35,6 +68,11 @@ QRect captureRectInSnapshot(const DesktopSnapshot& snapshot, const QRect& rect,
return snapshot.image().rect();
}
#ifdef Q_OS_WIN
Q_UNUSED(dpr);
const QRect localRect = rect.translated(-snapshot.virtualGeometry().topLeft());
return localRect.intersected(snapshot.image().rect());
#else
const QRect localRect = rect.translated(-snapshot.virtualGeometry().topLeft());
const QPoint topLeft = QPoint(qRound(localRect.left() * dpr),
qRound(localRect.top() * dpr));
@@ -43,6 +81,7 @@ QRect captureRectInSnapshot(const DesktopSnapshot& snapshot, const QRect& rect,
QRect target;
target.setCoords(topLeft.x(), topLeft.y(), bottomRight.x(), bottomRight.y());
return target.intersected(snapshot.image().rect());
#endif
}
}
@@ -140,6 +179,11 @@ QString ScreenshotViewModel::captureRectToStickyUrl(QQuickItem *paintBoard, cons
return {};
}
const qreal dpr = effectiveCaptureDpr(paintBoard, r);
if (dpr > 0.0) {
img.setDevicePixelRatio(dpr);
}
return m_stickyStore.storeImage(img);
}
@@ -199,11 +243,7 @@ QImage ScreenshotViewModel::captureScreen(QQuickItem *paintBoard,
qreal dpr = 1.0;
if (!m_snapshot.isNull()) {
if (board && board->window()) {
dpr = board->window()->devicePixelRatio();
} else if (!rect.isEmpty()) {
dpr = captureDprForRect(rect);
}
dpr = effectiveCaptureDpr(paintBoard, rect);
const QRect captureRect = captureRectInSnapshot(m_snapshot, rect, dpr);
screenshot = m_snapshot.image().copy(captureRect).toImage();
} else {
@@ -249,9 +289,9 @@ QImage ScreenshotViewModel::captureScreen(QQuickItem *paintBoard,
if (setBackground) {
QImage background = screenshot;
if (board->window() && board->window()->devicePixelRatio() > 1.0) {
const QSize logicalSize(qRound(background.width() / board->window()->devicePixelRatio()),
qRound(background.height() / board->window()->devicePixelRatio()));
if (dpr > 1.0) {
const QSize logicalSize(qRound(background.width() / dpr),
qRound(background.height() / dpr));
if (logicalSize.isValid() && logicalSize != background.size()) {
background = background.scaled(logicalSize,
Qt::IgnoreAspectRatio,
+70 -24
View File
@@ -1,10 +1,13 @@
#include "sticky_view_model.h"
#include <QPainter>
#include <QTransform>
#include "sticky_view_model.h"
#include "ai_view_model.h"
#include "widgets/sticky_pin_widget.h"
#include <QGuiApplication>
#include <QPainter>
#include <QScreen>
#include <QTransform>
#include <QWindow>
#include <QDebug>
#ifdef Q_OS_WIN
#include <windows.h>
@@ -16,6 +19,11 @@ StickyViewModel::StickyViewModel(StickyImageStore &store, QObject *parent)
{
}
void StickyViewModel::setAiViewModel(AIViewModel *aiViewModel)
{
m_aiViewModel = aiViewModel;
}
void StickyViewModel::requestSticky(const QString &imageUrl, const QRect &imgRect)
{
if (imageUrl.isEmpty()) {
@@ -23,16 +31,14 @@ void StickyViewModel::requestSticky(const QString &imageUrl, const QRect &imgRec
}
QRect targetRect = imgRect.normalized();
QScreen *screen = QGuiApplication::screenAt(targetRect.topLeft());
if (!screen) {
screen = QGuiApplication::screenAt(targetRect.center());
#ifdef Q_OS_WIN
const QImage image = m_store.getImageByUrl(imageUrl);
if (!image.isNull()) {
auto *widget = new StickyPinWidget(imageUrl, targetRect, image, &m_store, m_aiViewModel);
Q_UNUSED(widget);
return;
}
if (!screen) {
screen = QGuiApplication::primaryScreen();
}
QImage image = m_store.getImageByUrl(imageUrl);
#endif
emit stickyReady(imageUrl, targetRect);
}
@@ -51,22 +57,58 @@ void StickyViewModel::positionStickyWindow(QObject *windowObject, const QRect &i
const QRect targetRect = imgRect.normalized();
const QPoint topLeft = targetRect.topLeft();
#ifdef Q_OS_WIN
if (HWND hwnd = reinterpret_cast<HWND>(window->winId())) {
SetWindowPos(hwnd,
HWND_TOPMOST,
topLeft.x(),
topLeft.y(),
0,
0,
SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER);
return;
}
#endif
QScreen *screen = QGuiApplication::screenAt(topLeft);
if (!screen) {
screen = QGuiApplication::screenAt(targetRect.center());
}
if (!screen) {
screen = QGuiApplication::primaryScreen();
}
if (screen && window->screen() != screen) {
window->setScreen(screen);
}
window->setPosition(topLeft);
}
void StickyViewModel::resizeStickyWindow(QObject *windowObject, int physicalWidth, int physicalHeight) const
{
auto *window = qobject_cast<QWindow*>(windowObject);
if (!window) {
return;
}
const int targetWidth = qMax(1, physicalWidth);
const int targetHeight = qMax(1, physicalHeight);
#ifdef Q_OS_WIN
if (HWND hwnd = reinterpret_cast<HWND>(window->winId())) {
RECT rect{};
GetWindowRect(hwnd, &rect);
qDebug() << "[StickyHWND] qt-pos:"
<< topLeft
<< "hwnd:"
<< QRect(rect.left,
rect.top,
rect.right - rect.left,
rect.bottom - rect.top)
<< "qmlPos:" << window->x() << window->y();
SetWindowPos(hwnd,
nullptr,
0,
0,
targetWidth,
targetHeight,
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOOWNERZORDER);
return;
}
#endif
window->resize(targetWidth, targetHeight);
}
bool StickyViewModel::saveImage(const QString &imageUrl, const QUrl &targetUrl)
@@ -86,7 +128,11 @@ QImage StickyViewModel::getImageByUrl(const QString &imageUrl) const
QSize StickyViewModel::getImageSizeByUrl(const QString &imageUrl) const
{
return m_store.getImageByUrl(imageUrl).size();
const QImage image = m_store.getImageByUrl(imageUrl);
if (image.isNull()) {
return {};
}
return image.size();
}
QImage StickyViewModel::mergeLayers(const QString &imageUrl, const QImage &annotationLayer) const
+4
View File
@@ -7,6 +7,7 @@
#include "sticky_image_store.h"
class QScreen;
class AIViewModel;
// StickyViewModel
// ---------------
@@ -21,6 +22,7 @@ class StickyViewModel : public QObject
public:
explicit StickyViewModel(StickyImageStore &store, QObject *parent = nullptr);
void setAiViewModel(AIViewModel *aiViewModel);
// 通知 QML 创建贴图窗口,由 ScreenshotViewModel::captureRectToStickyUrl 产生的 URL 触发
// QML 监听 stickyReady(imageUrl, imgRect) 信号后创建 TZStickyWindow
@@ -33,6 +35,7 @@ public:
Q_INVOKABLE QImage getImageByUrl(const QString &imageUrl) const;
Q_INVOKABLE QSize getImageSizeByUrl(const QString &imageUrl) const;
Q_INVOKABLE void positionStickyWindow(QObject *windowObject, const QRect &imgRect) const;
Q_INVOKABLE void resizeStickyWindow(QObject *windowObject, int physicalWidth, int physicalHeight) const;
Q_INVOKABLE bool overwriteWithAnnotations(const QString &imageUrl, const QImage &annotationLayer);
Q_INVOKABLE bool saveMergedImage(const QString &imageUrl, const QImage &annotationLayer, const QUrl &targetUrl);
Q_INVOKABLE bool rotateImage(const QString &imageUrl, int degreesClockwise);
@@ -46,6 +49,7 @@ private:
QImage mergeLayers(const QString &imageUrl, const QImage &annotationLayer) const;
StickyImageStore &m_store;
AIViewModel *m_aiViewModel = nullptr;
};
#endif // STICKY_VIEW_MODEL_H
+262
View File
@@ -0,0 +1,262 @@
#include "widgets/sticky_canvas_widget.h"
#include "paint_board/shape/blur_shape.h"
#include "paint_board/shape/mosaic_shape.h"
#include <QMouseEvent>
#include <QPainter>
StickyCanvasWidget::StickyCanvasWidget(QWidget *parent)
: QWidget(parent)
{
setAttribute(Qt::WA_OpaquePaintEvent, false);
setAttribute(Qt::WA_TransparentForMouseEvents, true);
setMouseTracking(true);
}
StickyCanvasWidget::~StickyCanvasWidget()
{
clearShapes();
}
void StickyCanvasWidget::setBackgroundImage(const QImage &image)
{
m_backgroundImage = image;
for (Shape *shape : m_shapes) {
if (auto *mosaic = dynamic_cast<MosaicShape*>(shape)) {
mosaic->setCanvasSnapshot(m_backgroundImage);
} else if (auto *blur = dynamic_cast<BlurShape*>(shape)) {
blur->setCanvasSnapshot(m_backgroundImage);
}
}
update();
}
void StickyCanvasWidget::setViewScale(qreal scale)
{
const qreal next = qMax<qreal>(0.01, scale);
if (qFuzzyCompare(m_viewScale, next)) {
return;
}
m_viewScale = next;
update();
}
void StickyCanvasWidget::setActiveShapeType(Shapeype type)
{
m_shapeType = type;
}
void StickyCanvasWidget::setPenColor(const QColor &color)
{
m_penColor = color;
}
void StickyCanvasWidget::setPenSize(int size)
{
m_penSize = qMax(1, size);
}
void StickyCanvasWidget::setDrawingEnabled(bool enabled)
{
m_drawingEnabled = enabled;
setAttribute(Qt::WA_TransparentForMouseEvents, !m_drawingEnabled);
if (!m_drawingEnabled) {
finishCurrentShape();
}
}
void StickyCanvasWidget::setAnnotationText(const QString &text)
{
m_annotationText = text;
}
void StickyCanvasWidget::setTextBackgroundEnabled(bool enabled)
{
m_textBackgroundEnabled = enabled;
}
void StickyCanvasWidget::setNumberValue(int value)
{
m_numberValue = qMax(1, value);
}
void StickyCanvasWidget::setNumberAutoIncrement(bool enabled)
{
m_numberAutoIncrement = enabled;
}
void StickyCanvasWidget::addTextAnnotation(const QPoint &point, const QString &text)
{
const QString resolved = text.trimmed();
if (resolved.isEmpty()) {
return;
}
const QPoint imagePoint(qRound(point.x() / m_viewScale),
qRound(point.y() / m_viewScale));
Shape *textShape = m_factory.createTextShape(imagePoint,
resolved,
m_penColor,
m_penSize,
m_textBackgroundEnabled);
if (!textShape) {
return;
}
m_shapes.append(textShape);
m_annotationText = resolved;
update();
}
void StickyCanvasWidget::undo()
{
if (!m_shapes.isEmpty()) {
delete m_shapes.takeLast();
update();
}
}
void StickyCanvasWidget::reset()
{
finishCurrentShape();
clearShapes();
m_backgroundImage = QImage();
update();
}
void StickyCanvasWidget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.fillRect(rect(), Qt::white);
if (!m_backgroundImage.isNull()) {
painter.drawImage(rect(), m_backgroundImage);
}
painter.save();
painter.scale(m_viewScale, m_viewScale);
for (Shape *shape : m_shapes) {
if (shape) {
shape->draw(&painter);
}
}
if (m_currentShape) {
m_currentShape->draw(&painter);
}
painter.restore();
}
void StickyCanvasWidget::mousePressEvent(QMouseEvent *event)
{
if (!m_drawingEnabled || event->button() != Qt::LeftButton) {
QWidget::mousePressEvent(event);
return;
}
const QPoint displayPoint = event->position().toPoint();
m_startPoint = QPoint(qRound(displayPoint.x() / m_viewScale),
qRound(displayPoint.y() / m_viewScale));
if (m_shapeType == TEXT) {
emit textPlacementRequested(displayPoint);
return;
}
if (m_shapeType == NUMBER) {
Shape *numberShape = m_factory.createNumberShape(m_startPoint,
m_numberValue,
m_penColor,
m_penSize);
if (numberShape) {
m_shapes.append(numberShape);
if (m_numberAutoIncrement) {
++m_numberValue;
emit numberValueChanged(m_numberValue);
}
update();
}
return;
}
delete m_currentShape;
m_currentShape = nullptr;
if (m_shapeType == MOSAIC) {
auto *mosaic = new MosaicShape(m_startPoint,
qMax(10, m_penSize * 5),
qMax(2, m_penSize));
mosaic->setCanvasSnapshot(m_backgroundImage);
m_currentShape = mosaic;
m_draggingShape = true;
update();
return;
}
if (m_shapeType == BLUR) {
auto *blur = new BlurShape(m_startPoint,
qMax(10, m_penSize * 5),
qMax(2, m_penSize));
blur->setCanvasSnapshot(m_backgroundImage);
m_currentShape = blur;
m_draggingShape = true;
update();
return;
}
m_currentShape = m_factory.createShape(m_shapeType,
m_startPoint,
m_startPoint,
m_penColor,
m_penSize);
m_draggingShape = (m_currentShape != nullptr);
update();
}
void StickyCanvasWidget::mouseMoveEvent(QMouseEvent *event)
{
if (!m_draggingShape || !m_currentShape) {
QWidget::mouseMoveEvent(event);
return;
}
const QPoint imagePoint(qRound(event->position().x() / m_viewScale),
qRound(event->position().y() / m_viewScale));
m_currentShape->setEndPoint(imagePoint);
update();
}
void StickyCanvasWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (!m_draggingShape || !m_currentShape || event->button() != Qt::LeftButton) {
QWidget::mouseReleaseEvent(event);
return;
}
const QPoint imagePoint(qRound(event->position().x() / m_viewScale),
qRound(event->position().y() / m_viewScale));
m_currentShape->setEndPoint(imagePoint);
m_shapes.append(m_currentShape);
m_currentShape = nullptr;
m_draggingShape = false;
update();
}
void StickyCanvasWidget::finishCurrentShape()
{
if (m_currentShape) {
delete m_currentShape;
m_currentShape = nullptr;
}
m_draggingShape = false;
}
void StickyCanvasWidget::clearShapes()
{
qDeleteAll(m_shapes);
m_shapes.clear();
}
+68
View File
@@ -0,0 +1,68 @@
#ifndef STICKY_CANVAS_WIDGET_H
#define STICKY_CANVAS_WIDGET_H
#include <QColor>
#include <QImage>
#include <QPoint>
#include <QWidget>
#include "paint_board/shape/shape.h"
#include "paint_board/shape/shape_factory.h"
#include "paint_board/shape/shape_type.h"
class StickyCanvasWidget : public QWidget
{
Q_OBJECT
public:
explicit StickyCanvasWidget(QWidget *parent = nullptr);
~StickyCanvasWidget() override;
void setBackgroundImage(const QImage &image);
void setViewScale(qreal scale);
void setActiveShapeType(Shapeype type);
void setPenColor(const QColor &color);
void setPenSize(int size);
void setDrawingEnabled(bool enabled);
void setAnnotationText(const QString &text);
void setTextBackgroundEnabled(bool enabled);
void setNumberValue(int value);
void setNumberAutoIncrement(bool enabled);
bool drawingEnabled() const { return m_drawingEnabled; }
Shapeype activeShapeType() const { return m_shapeType; }
void addTextAnnotation(const QPoint &point, const QString &text);
void undo();
void reset();
signals:
void numberValueChanged(int value);
void textPlacementRequested(const QPoint &point);
protected:
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
private:
void finishCurrentShape();
void clearShapes();
ShapeFactory m_factory;
QList<Shape*> m_shapes;
Shape *m_currentShape = nullptr;
QImage m_backgroundImage;
QColor m_penColor = Qt::red;
int m_penSize = 6;
Shapeype m_shapeType = PEN;
QString m_annotationText = QStringLiteral("Text");
bool m_textBackgroundEnabled = true;
bool m_numberAutoIncrement = true;
int m_numberValue = 1;
bool m_drawingEnabled = false;
bool m_draggingShape = false;
qreal m_viewScale = 1.0;
QPoint m_startPoint;
};
#endif // STICKY_CANVAS_WIDGET_H
File diff suppressed because it is too large. Load diff
+116
View File
@@ -0,0 +1,116 @@
#ifndef STICKY_PIN_WIDGET_H
#define STICKY_PIN_WIDGET_H
#include <QImage>
#include <QPointer>
#include <QRect>
#include <QSize>
#include <QVector>
#include <QWidget>
#include "paint_board/shape/shape_type.h"
class StickyImageStore;
class AIViewModel;
class QCheckBox;
class QLabel;
class QLineEdit;
class QSpinBox;
class QToolButton;
class StickyCanvasWidget;
class StickyPinWidget : public QWidget
{
Q_OBJECT
public:
StickyPinWidget(const QString &imageUrl,
const QRect &physicalRect,
const QImage &image,
StickyImageStore *store,
AIViewModel *aiViewModel = nullptr,
QWidget *parent = nullptr);
~StickyPinWidget() override;
protected:
void paintEvent(QPaintEvent *event) override;
void closeEvent(QCloseEvent *event) override;
void contextMenuEvent(QContextMenuEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
private:
void setActiveTool(Shapeype type, QToolButton *activeButton);
void syncCanvasToolSettings();
void updateToolOptionsPanel();
void updateLayoutAndSize();
QToolButton *currentActiveToolButton() const;
void setZoomFactor(qreal value);
void openAiEditor();
void applyAiImage(const QString &oldImageUrl, const QString &newImageUrl);
void releaseStoredImage();
bool saveCurrentImage();
qreal screenScaleForRect(const QRect &physicalRect) const;
void applyNativePosition(const QRect &physicalRect);
QPoint currentContentTopLeftPhysical() const;
QRect contentRect() const;
QRect toolOptionsRect() const;
QRect toolbarRect() const;
int shadowPadding() const;
int toolbarGap() const;
int toolOptionsGap() const;
int toolOptionsHeight() const;
int toolOptionsWidth() const;
int toolbarHeight() const;
int toolbarWidth() const;
QString m_imageUrl;
QRect m_physicalRect;
QImage m_image;
QSize m_baseImageDisplaySize;
QSize m_imageDisplaySize;
QPointer<StickyImageStore> m_store;
QPointer<AIViewModel> m_aiViewModel;
StickyCanvasWidget *m_canvas = nullptr;
QWidget *m_toolOptions = nullptr;
QWidget *m_toolbar = nullptr;
QLabel *m_zoomLabel = nullptr;
QVector<QToolButton*> m_colorButtons;
QVector<QToolButton*> m_sizeButtons;
QLineEdit *m_inlineTextEditor = nullptr;
QLineEdit *m_textInput = nullptr;
QCheckBox *m_textBackgroundCheck = nullptr;
QCheckBox *m_numberAutoIncrementCheck = nullptr;
QToolButton *m_numberMinusButton = nullptr;
QLabel *m_numberLabel = nullptr;
QToolButton *m_numberPlusButton = nullptr;
QSpinBox *m_numberValueSpin = nullptr;
QToolButton *m_pencilButton = nullptr;
QToolButton *m_rectButton = nullptr;
QToolButton *m_circleButton = nullptr;
QToolButton *m_arrowButton = nullptr;
QToolButton *m_highlightButton = nullptr;
QToolButton *m_mosaicButton = nullptr;
QToolButton *m_blurButton = nullptr;
QToolButton *m_textButton = nullptr;
QToolButton *m_numberButton = nullptr;
QToolButton *m_undoButton = nullptr;
QToolButton *m_aiButton = nullptr;
QToolButton *m_copyButton = nullptr;
QToolButton *m_saveButton = nullptr;
QToolButton *m_toggleToolbarButton = nullptr;
QToolButton *m_closeButton = nullptr;
QColor m_currentPenColor = QColor("#F43F5E");
int m_currentPenSize = 6;
QString m_annotationText = QStringLiteral("文本");
QPoint m_pendingTextPoint;
bool m_textBackgroundEnabled = true;
bool m_numberAutoIncrement = true;
int m_numberValue = 1;
bool m_toolbarVisible = true;
bool m_shadowVisible = true;
qreal m_zoomFactor = 1.0;
bool m_released = false;
};
#endif // STICKY_PIN_WIDGET_H