@@ -5,12 +5,25 @@ import { gettext as _ } from '~/shared/i18n.ts';
55import * as Main from '@girs/gnome-shell/ui/main' ;
66import { Slider } from '@girs/gnome-shell/ui/slider' ;
77import { AnnotationCanvas } from '~/capture/annotationCanvas.ts' ;
8- import { AnnotationModel , type AnnotationTool , type Point } from '~/capture/annotationModel.ts' ;
8+ import {
9+ AnnotationModel ,
10+ type Annotation ,
11+ type AnnotationTool ,
12+ type Point ,
13+ } from '~/capture/annotationModel.ts' ;
914import { OcrController , OcrUnavailableError } from '~/capture/ocrController.ts' ;
1015import { buildWebSearchUri , placeOcrActionBelow , type OcrWord } from '~/capture/ocrLogic.ts' ;
1116import { CaptureTooltip } from '~/capture/captureTooltip.ts' ;
12- import { captureScreenshot , exportAnnotatedScreenshot } from '~/capture/screenshotCapture.ts' ;
13- import { getScreenshotUi , type ScreenshotUi } from '~/capture/screenshotUiAdapter.ts' ;
17+ import {
18+ captureScreenshot ,
19+ exportAnnotatedScreenshot ,
20+ type CapturedScreenshot ,
21+ } from '~/capture/screenshotCapture.ts' ;
22+ import {
23+ getScreenshotUi ,
24+ type Geometry ,
25+ type ScreenshotUi ,
26+ } from '~/capture/screenshotUiAdapter.ts' ;
1427import type { ExtensionContext } from '~/core/context.ts' ;
1528import { LifecycleScope } from '~/core/lifecycleScope.ts' ;
1629import { logger } from '~/core/logger.ts' ;
@@ -99,6 +112,7 @@ export class CaptureTools extends Module {
99112 private _textPoint : Point | null = null ;
100113 private _toolbarDrag : ToolbarDrag | null = null ;
101114 private _toolbarGrab : Clutter . Grab | null = null ;
115+ private _toolbarDraggedByUser = false ;
102116 private _ocrText = '' ;
103117 private _ocrBusy = false ;
104118 private _devOcrAvailable : boolean | null = null ;
@@ -222,6 +236,7 @@ export class CaptureTools extends Module {
222236 this . _portalMode = false ;
223237 this . _toolbarDrag = null ;
224238 this . _toolbarGrab = null ;
239+ this . _toolbarDraggedByUser = false ;
225240 this . _originalOpen = null ;
226241 this . _openWrapper = null ;
227242 this . _originalSaveScreenshot = null ;
@@ -235,11 +250,17 @@ export class CaptureTools extends Module {
235250 scope . connect ( button , 'notify::checked' , ( ) => this . _syncVisibility ( ) ) ;
236251 for ( const button of [ ui . _selectionButton , ui . _screenButton , ui . _windowButton ] )
237252 scope . connect ( button , 'notify::checked' , ( ) => this . _clearOcr ( ) ) ;
253+ scope . connect ( ui . _selectionButton , 'notify::checked' , ( ) => this . _syncToolbarPlacement ( ) ) ;
238254 scope . connect ( ui . _areaSelector , 'drag-started' , ( ) => {
239255 this . _clearOcr ( ) ;
240256 this . _setInteractionState ( 'selection' ) ;
241257 } ) ;
242- scope . connect ( ui . _areaSelector , 'drag-ended' , ( ) => this . _setInteractionState ( 'idle' ) ) ;
258+ scope . connect ( ui . _areaSelector , 'drag-ended' , ( ) => {
259+ this . _setInteractionState ( 'idle' ) ;
260+ this . _syncToolbarPlacement ( ) ;
261+ } ) ;
262+ if ( this . _toolbar )
263+ scope . connect ( this . _toolbar , 'notify::allocation' , ( ) => this . _syncToolbarPlacement ( ) ) ;
243264
244265 const monitorsChangedId = Main . layoutManager . connect ( 'monitors-changed' , ( ) => {
245266 this . _endToolbarDrag ( ) ;
@@ -289,18 +310,12 @@ export class CaptureTools extends Module {
289310 const model = this . _model ;
290311 if ( this . _portalMode || ! model ?. hasAnnotations ) return original . call ( ui ) ;
291312
313+ const annotations = [ ...model . annotations ] ;
292314 try {
293315 const capture = await captureScreenshot ( ui ) ;
294- const file = await exportAnnotatedScreenshot (
295- capture . pixbuf ,
296- model . annotations ,
297- { origin : capture . origin , scale : capture . scale } ,
298- { copy : true , save : true } ,
299- ) ;
300- if ( file ) ui . emit ( 'screenshot-taken' , file ) ;
301- logger . debug ( `Saved screenshot with ${ model . annotations . length } annotation(s)` , {
302- prefix : LOG_PREFIX ,
303- } ) ;
316+ // Return now so the caller closes the UI with the default animation;
317+ // the annotated export finishes in the background.
318+ void this . _exportAnnotatedScreenshot ( ui , capture , annotations ) ;
304319 } catch ( error ) {
305320 logger . warn ( `[CaptureTools] Annotated screenshot export failed: ${ String ( error ) } ` ) ;
306321 Main . notify ( _ ( 'Screenshot failed' ) , _ ( 'Could not export the annotated screenshot' ) ) ;
@@ -310,6 +325,28 @@ export class CaptureTools extends Module {
310325 ui . _saveScreenshot = wrapper ;
311326 }
312327
328+ private async _exportAnnotatedScreenshot (
329+ ui : ScreenshotUi ,
330+ capture : CapturedScreenshot ,
331+ annotations : readonly Annotation [ ] ,
332+ ) : Promise < void > {
333+ try {
334+ const file = await exportAnnotatedScreenshot (
335+ capture . pixbuf ,
336+ annotations ,
337+ { origin : capture . origin , scale : capture . scale } ,
338+ { copy : true , save : true } ,
339+ ) ;
340+ if ( file ) ui . emit ( 'screenshot-taken' , file ) ;
341+ logger . debug ( `Saved screenshot with ${ annotations . length } annotation(s)` , {
342+ prefix : LOG_PREFIX ,
343+ } ) ;
344+ } catch ( error ) {
345+ logger . warn ( `[CaptureTools] Annotated screenshot export failed: ${ String ( error ) } ` ) ;
346+ Main . notify ( _ ( 'Screenshot failed' ) , _ ( 'Could not export the annotated screenshot' ) ) ;
347+ }
348+ }
349+
313350 private _buildToolbar ( ) : St . BoxLayout {
314351 const toolbar = new St . BoxLayout ( {
315352 style_class : 'screenshot-ui-panel capture-tools-toolbar' ,
@@ -496,6 +533,7 @@ export class CaptureTools extends Module {
496533 private _releaseToolbar ( event : Clutter . Event ) : boolean {
497534 if ( ! this . _toolbarDrag || event . get_button ( ) !== Clutter . BUTTON_PRIMARY )
498535 return Clutter . EVENT_PROPAGATE ;
536+ this . _toolbarDraggedByUser = true ;
499537 this . _endToolbarDrag ( ) ;
500538 return Clutter . EVENT_STOP ;
501539 }
@@ -697,6 +735,51 @@ export class CaptureTools extends Module {
697735 this . _canvas ?. setDrawingEnabled ( this . _model ? this . _isDrawingTool ( this . _model . tool ) : false ) ;
698736 }
699737 this . _syncOcrButton ( ) ;
738+ if ( visible ) this . _syncToolbarPlacement ( ) ;
739+ }
740+
741+ private _syncToolbarPlacement ( ) : void {
742+ const toolbar = this . _toolbar ;
743+ const ui = this . _ui ;
744+ if ( ! toolbar || ! ui || ! toolbar . visible || toolbar . width === 0 ) return ;
745+ // A position picked manually through the drag handle wins for the session.
746+ if ( this . _toolbarDraggedByUser ) return ;
747+
748+ const monitor = Main . layoutManager . primaryMonitor ;
749+ if ( ! monitor ) return ;
750+
751+ let selection : Geometry | null = null ;
752+ if ( ! this . _portalMode && ui . _shotButton . checked && ui . _selectionButton . checked ) {
753+ try {
754+ const [ x , y , width , height ] = ui . _areaSelector . getGeometry ( ) ;
755+ if ( width > 0 && height > 0 ) selection = [ x , y , width , height ] ;
756+ } catch {
757+ // The selector may not have geometry before its first allocation.
758+ }
759+ }
760+
761+ if ( ! selection ) {
762+ toolbar . translation_x = 0 ;
763+ toolbar . translation_y = 0 ;
764+ return ;
765+ }
766+
767+ // Anchor to the aligned top-center slot the toolbar is allocated in, then
768+ // translate so it floats just above the selection rectangle.
769+ const [ stageX , stageY ] = toolbar . get_transformed_position ( ) ;
770+ const anchorX = stageX - toolbar . translation_x ;
771+ const anchorY = stageY - toolbar . translation_y ;
772+ const [ x , y , width , height ] = selection ;
773+ const margin = 12 ;
774+ const desiredX = Math . max (
775+ monitor . x ,
776+ Math . min ( x + width / 2 - toolbar . width / 2 , monitor . x + monitor . width - toolbar . width ) ,
777+ ) ;
778+ let desiredY = y - toolbar . height - margin ;
779+ if ( desiredY < monitor . y ) desiredY = y + height + margin ;
780+ desiredY = Math . max ( monitor . y , Math . min ( desiredY , monitor . y + monitor . height - toolbar . height ) ) ;
781+ toolbar . translation_x = Math . round ( desiredX - anchorX ) ;
782+ toolbar . translation_y = Math . round ( desiredY - anchorY ) ;
700783 }
701784
702785 private _setControlsOpacity ( opacity : number ) : void {
@@ -742,6 +825,7 @@ export class CaptureTools extends Module {
742825 this . _portalMode = false ;
743826 this . _selectTool ( 'select' ) ;
744827 this . _resetControlsOpacity ( ) ;
828+ this . _toolbarDraggedByUser = false ;
745829 if ( this . _toolbar ) {
746830 this . _toolbar . translation_x = 0 ;
747831 this . _toolbar . translation_y = 0 ;
0 commit comments