diff --git a/Forms/MainWindow.frm b/Forms/MainWindow.frm index 0dc73d9f1..906b00edd 100644 --- a/Forms/MainWindow.frm +++ b/Forms/MainWindow.frm @@ -1734,9 +1734,17 @@ Begin VB.Form FormMain Index = 8 End Begin VB.Menu MnuView - Caption = "Snap to canvas edges" + Caption = "Snap" Index = 9 End + Begin VB.Menu MnuView + Caption = "Snap to" + Index = 10 + Begin VB.Menu MnuSnap + Caption = "Canvas edges" + Index = 0 + End + End End Begin VB.Menu MnuWindowTop Caption = "Window" @@ -3741,6 +3749,13 @@ Private Sub MnuSharpen_Click(Index As Integer) End Select End Sub +Private Sub MnuSnap_Click(Index As Integer) + Select Case Index + Case 0 + Actions.LaunchAction_ByName "snap_canvasedge" + End Select +End Sub + Private Sub MnuSpecificZoom_Click(Index As Integer) Select Case Index Case 0 @@ -3829,7 +3844,7 @@ Private Sub MnuView_Click(Index As Integer) Case 3 Actions.LaunchAction_ByName "view_zoomout" Case 4 - 'zoom-to-value top-level menu + 'zoom-to-value top-level Case 5 '(separator) Case 6 @@ -3839,7 +3854,9 @@ Private Sub MnuView_Click(Index As Integer) Case 8 '(separator) Case 9 - Actions.LaunchAction_ByName "snap_canvasedge" + Actions.LaunchAction_ByName "snap_global" + Case 10 + 'snap-to top-level End Select End Sub diff --git a/Forms/Tools_Options.frm b/Forms/Tools_Options.frm index 0aca45f75..4e35d8b0e 100644 --- a/Forms/Tools_Options.frm +++ b/Forms/Tools_Options.frm @@ -1206,7 +1206,7 @@ Private Sub cmdBarMini_OKClick() End If UserPrefs.SetPref_Long "Interface", "snap-distance", spnSnapDistance.Value - Tools_Move.SetSnapDistance spnSnapDistance.Value + Tools_Move.SetSnap_Distance spnSnapDistance.Value UserPrefs.SetPref_Long "Transparency", "Alpha Check Mode", CLng(cboAlphaCheck.ListIndex) UserPrefs.SetPref_Long "Transparency", "Alpha Check One", CLng(csAlphaOne.Color) diff --git a/Modules/Actions.bas b/Modules/Actions.bas index a4cfb1492..e69170945 100644 --- a/Modules/Actions.bas +++ b/Modules/Actions.bas @@ -1353,6 +1353,9 @@ Private Function Launch_ByName_MenuView(ByRef srcMenuName As String, Optional By newState = Not FormMain.MainCanvas(0).GetStatusBarVisibility() FormMain.MnuView(7).Checked = newState FormMain.MainCanvas(0).SetStatusBarVisibility newState + + Case "snap_global" + Interface.ToggleSnapOptions pdst_Global Case "snap_canvasedge" Interface.ToggleSnapOptions pdst_CanvasEdge @@ -2002,6 +2005,7 @@ Public Sub BuildActionDatabase() AddAction "zoom_1_16", vbNullString AddAction "view_rulers", vbNullString AddAction "view_statusbar", vbNullString + AddAction "snap_global", vbNullString AddAction "snap_canvasedge", vbNullString 'AddAction "window_toolbox" AddAction "window_displaytoolbox", vbNullString diff --git a/Modules/Hotkeys.bas b/Modules/Hotkeys.bas index 92dd3bf0c..f07d7da07 100644 --- a/Modules/Hotkeys.bas +++ b/Modules/Hotkeys.bas @@ -310,6 +310,7 @@ Public Sub InitializeDefaultHotkeys() Hotkeys.AddHotkey vbKey3, vbShiftMask, "zoom_1_4" Hotkeys.AddHotkey vbKey4, vbShiftMask, "zoom_1_8" Hotkeys.AddHotkey vbKey5, vbShiftMask, "zoom_1_16" + Hotkeys.AddHotkey VK_OEM_1, vbCtrlMask Or vbShiftMask, "snap_global" 'Window menu Hotkeys.AddHotkey vbKeyPageDown, , "window_next" diff --git a/Modules/Interface.bas b/Modules/Interface.bas index 20448afdc..925749394 100644 --- a/Modules/Interface.bas +++ b/Modules/Interface.bas @@ -391,18 +391,18 @@ Public Sub SyncUI_CurrentLayerSettings() nonDestructiveResizeActive = (PDImages.GetActiveImage.GetActiveLayer.GetLayerCanvasXModifier <> 1#) Or (PDImages.GetActiveImage.GetActiveLayer.GetLayerCanvasYModifier <> 1#) 'If non-destructive resizing is active, the "reset layer size" menu (and corresponding Move Tool button) must be enabled. - Menus.SetMenuEnabled "layer_resetsize", nonDestructiveResizeActive + If (Menus.IsMenuEnabled("layer_resetsize") <> nonDestructiveResizeActive) Then Menus.SetMenuEnabled "layer_resetsize", nonDestructiveResizeActive If (g_CurrentTool = NAV_MOVE) Then toolpanel_MoveSize.cmdLayerAffinePermanent.Enabled = PDImages.GetActiveImage.GetActiveLayer.AffineTransformsActive(True) End If 'Layer visibility - Menus.SetMenuChecked "layer_show", PDImages.GetActiveImage.GetActiveLayer.GetLayerVisibility() + If (Menus.IsMenuChecked("layer_show") <> PDImages.GetActiveImage.GetActiveLayer.GetLayerVisibility()) Then Menus.SetMenuChecked "layer_show", PDImages.GetActiveImage.GetActiveLayer.GetLayerVisibility() 'Layer rasterization depends on the current layer type - Menus.SetMenuEnabled "layer_rasterizecurrent", PDImages.GetActiveImage.GetActiveLayer.IsLayerVector - Menus.SetMenuEnabled "layer_rasterizeall", (PDImages.GetActiveImage.GetNumOfVectorLayers > 0) + If (Menus.IsMenuEnabled("layer_rasterizecurrent") <> PDImages.GetActiveImage.GetActiveLayer.IsLayerVector) Then Menus.SetMenuEnabled "layer_rasterizecurrent", PDImages.GetActiveImage.GetActiveLayer.IsLayerVector + If (Menus.IsMenuEnabled("layer_rasterizeall") <> (PDImages.GetActiveImage.GetNumOfVectorLayers > 0)) Then Menus.SetMenuEnabled "layer_rasterizeall", (PDImages.GetActiveImage.GetNumOfVectorLayers > 0) End Sub @@ -732,6 +732,8 @@ Public Sub SetUIGroupState(ByVal metaItem As PD_UI_Group, ByVal newState As Bool 'View (top-menu level) Case PDUI_View Menus.SetMenuEnabled "view_top", newState + Menus.SetMenuChecked "snap_global", Tools_Move.GetSnap_Global() + Menus.SetMenuChecked "snap_canvasedge", Tools_Move.GetSnap_CanvasEdge() 'ImageOps is all Image-related menu items; it enables/disables the Image, Layer, Select, Color, and Print menus. ' (This flag is very useful for items that require at least one open image to operate.) @@ -1214,24 +1216,24 @@ End Sub ' To forcibly set to a specific state (instead of toggling), set the forceInsteadOfToggle param to TRUE. Public Sub ToggleSnapOptions(ByVal snapTarget As PD_SnapTargets, Optional ByVal forceInsteadOfToggle As Boolean = False, Optional ByVal newState As Boolean = True) - 'Convert the snap target into a menu index - Const IDX_BASE As Long = 9 - Dim idxTarget As Long - 'While calculating which on-screen menu to update, we also need to relay changes to two places: ' 1) the tools_move module (which handles actual snap calculations) ' 2) the user preferences file (to ensure everything is synchronized between sessions) Select Case snapTarget + Case pdst_Global + If (Not forceInsteadOfToggle) Then newState = Not Tools_Move.GetSnap_Global() + Tools_Move.SetSnap_Global newState + UserPrefs.SetPref_Boolean "Interface", "snap-global", newState + Menus.SetMenuChecked "snap_global", newState + Case pdst_CanvasEdge - idxTarget = IDX_BASE + 0 - If (Not forceInsteadOfToggle) Then newState = Not Tools_Move.GetSnapCanvasEdge() - Tools_Move.SetSnapCanvasEdge newState + If (Not forceInsteadOfToggle) Then newState = Not Tools_Move.GetSnap_CanvasEdge() + Tools_Move.SetSnap_CanvasEdge newState UserPrefs.SetPref_Boolean "Interface", "snap-canvas-edge", newState + Menus.SetMenuChecked "snap_canvasedge", newState + End Select - 'Update the target menu state - FormMain.MnuView(idxTarget).Checked = newState - End Sub Public Function FixDPI(ByVal pxMeasurement As Long) As Long diff --git a/Modules/Menus.bas b/Modules/Menus.bas index 0e03bd910..b50a39d68 100644 --- a/Modules/Menus.bas +++ b/Modules/Menus.bas @@ -591,7 +591,9 @@ Public Sub InitializeMenus() AddMenuItem "Show rulers", "view_rulers", 8, 6 AddMenuItem "Show status bar", "view_statusbar", 8, 7 AddMenuItem "-", "-", 8, 8 - AddMenuItem "Snap to canvas edge", "snap_canvasedge", 8, 9 + AddMenuItem "Snap", "snap_global", 8, 9 + AddMenuItem "Snap to", "snap_top", 8, 10, allowInSearches:=False + AddMenuItem "Canvas edge", "snap_canvasedge", 8, 10, 0 'Window Menu AddMenuItem "Window", "window_top", 9 @@ -1657,8 +1659,11 @@ End Function 'Helper check for resolving menu enablement by menu name. Note that PD *does not* enforce unique menu names; in fact, they are ' specifically allowed by design. As such, this function only returns the *first* matching entry, with the assumption that ' same-named menus are enabled and disabled as a group. -Public Function SetMenuChecked(ByRef mnuName As String, Optional ByVal isChecked As Boolean = True) As Boolean - +Public Sub SetMenuChecked(ByRef mnuName As String, Optional ByVal isChecked As Boolean = True) + + 'Avoid redundant calls + If (Menus.IsMenuChecked(mnuName) = isChecked) Then Exit Sub + 'Resolve the menu name into an index into our menu collection Dim mnuIndex As Long If GetIndexFromName(mnuName, mnuIndex) Then @@ -1691,10 +1696,13 @@ Public Function SetMenuChecked(ByRef mnuName As String, Optional ByVal isChecked End If -End Function +End Sub Public Sub SetMenuEnabled(ByRef mnuName As String, Optional ByVal isEnabled As Boolean = True) - + + 'Avoid redundant calls + If (Menus.IsMenuEnabled(mnuName) = isEnabled) Then Exit Sub + 'Resolve the menu name into an index into our menu collection Dim mnuIndex As Long If GetIndexFromName(mnuName, mnuIndex) Then diff --git a/Modules/MoveTool.bas b/Modules/MoveTool.bas index de8cc2275..c1b8866ac 100644 --- a/Modules/MoveTool.bas +++ b/Modules/MoveTool.bas @@ -19,18 +19,19 @@ Attribute VB_Name = "Tools_Move" Option Explicit Public Enum PD_SnapTargets - pdst_CanvasEdge + pdst_Global = 0 + pdst_CanvasEdge = 1 End Enum #If False Then - Private Const pdst_CanvasEdge = 0 + Private Const pdst_Global = 0, pdst_CanvasEdge = 1 #End If 'The move/size tool exposes a number of UI-only options (like drawing borders around active layers). ' To improve viewport performance, we cache those settings locally, and the viewport queries us instead ' of directly querying the associated UI elements. Private m_DrawLayerBorders As Boolean, m_DrawCornerNodes As Boolean, m_DrawRotateNodes As Boolean -Private m_SnapToCanvasEdge As Boolean, m_SnapDistance As Long +Private m_SnapGlobal As Boolean, m_SnapToCanvasEdge As Boolean, m_SnapDistance As Long 'Same goes for various selection-related move settings (for moving selected pixels). These are simple ' flags whose value is relayed from the Move/Size options panel. @@ -325,19 +326,37 @@ Public Function GetDrawLayerRotateNodes() As Boolean GetDrawLayerRotateNodes = m_DrawRotateNodes End Function -Public Function GetSnapCanvasEdge() As Boolean - GetSnapCanvasEdge = m_SnapToCanvasEdge +'Returns TRUE if *any* snap-to-edge behaviors are enabled. Useful for skipping all snap checks. +Public Function GetSnap_Any() As Boolean + GetSnap_Any = m_SnapGlobal + If m_SnapGlobal Then + GetSnap_Any = m_SnapToCanvasEdge + 'TODO: OR against other snap options when added + End If +End Function + +Public Function GetSnap_CanvasEdge() As Boolean + GetSnap_CanvasEdge = m_SnapToCanvasEdge End Function -Public Function GetSnapDistance() As Long +Public Function GetSnap_Distance() As Long - GetSnapDistance = m_SnapDistance + GetSnap_Distance = m_SnapDistance 'Failsafe only; should never trigger - If (GetSnapDistance < 1) Then GetSnapDistance = 8 + If (GetSnap_Distance < 1) Then GetSnap_Distance = 8 End Function +'Returns TRUE if the top-level "View > Snap" menu is checked. Note that the user can enable/disable +' individual snap targets regardless of this setting, but if this setting is FALSE, we must ignore all +' other snap options. (This is how Photoshop behaves; the top-level Snap setting is mapped to a +' keyboard accelerator so the user can quickly enable/disable snap behavior without losing current +' per-target snap settings.) +Public Function GetSnap_Global() As Boolean + GetSnap_Global = m_SnapGlobal +End Function + Public Sub SetDrawLayerBorders(ByVal newState As Boolean) m_DrawLayerBorders = newState End Sub @@ -350,16 +369,20 @@ Public Sub SetDrawLayerRotateNodes(ByVal newState As Boolean) m_DrawRotateNodes = newState End Sub -Public Sub SetSnapCanvasEdge(ByVal newState As Boolean) +Public Sub SetSnap_CanvasEdge(ByVal newState As Boolean) m_SnapToCanvasEdge = newState End Sub -Public Sub SetSnapDistance(ByVal newDistance As Long) +Public Sub SetSnap_Distance(ByVal newDistance As Long) m_SnapDistance = newDistance If (m_SnapDistance < 1) Then m_SnapDistance = 1 If (m_SnapDistance > 255) Then m_SnapDistance = 255 'GIMP uses a 255 max value; that seems reasonable End Sub +Public Sub SetSnap_Global(ByVal newState As Boolean) + m_SnapGlobal = newState +End Sub + 'Relay functions for move selected pixels behavior Public Function GetMoveSelectedPixels_DefaultCut() As Boolean GetMoveSelectedPixels_DefaultCut = m_SelectionDefaultCut diff --git a/Modules/Tools.bas b/Modules/Tools.bas index 12442f64f..1097b11d2 100644 --- a/Modules/Tools.bas +++ b/Modules/Tools.bas @@ -257,7 +257,7 @@ Private Sub SnapPointByMoving(ByRef srcPointF As PointFloat, ByRef dstPointF As dstPointF = srcPointF 'Skip any further processing if the user hasn't enabled snapping - If (Not Tools_Move.GetSnapCanvasEdge()) Then Exit Sub + If (Not Tools_Move.GetSnap_Any()) Then Exit Sub 'Start by constructing a list of potential snap targets, based on current user settings. Dim xSnaps() As SnapComparison, ySnaps() As SnapComparison, numXSnaps As Long, numYSnaps As Long @@ -299,7 +299,7 @@ Private Sub SnapPointByMoving(ByRef srcPointF As PointFloat, ByRef dstPointF As 'Determine the minimum snap distance required for this zoom value. Dim snapThreshold As Double - snapThreshold = Tools_Move.GetSnapDistance() * (1# / Zoom.GetZoomRatioFromIndex(PDImages.GetActiveImage.ImgViewport.GetZoomIndex)) + snapThreshold = Tools_Move.GetSnap_Distance() * (1# / Zoom.GetZoomRatioFromIndex(PDImages.GetActiveImage.ImgViewport.GetZoomIndex)) 'If the minimum value falls beneath the minimum snap distance, snap away! If (minDistX < snapThreshold) Then dstPointF.x = xSnaps(idxSmallestX).cValue @@ -316,7 +316,7 @@ Private Sub SnapRectByMoving(ByRef srcRectF As RectF, ByRef dstRectF As RectF) dstRectF = srcRectF 'Skip any further processing if the user hasn't enabled snapping - If (Not Tools_Move.GetSnapCanvasEdge()) Then Exit Sub + If (Not Tools_Move.GetSnap_Any()) Then Exit Sub 'Start by constructing a list of potential snap targets, based on current user settings. Dim xSnaps() As SnapComparison, ySnaps() As SnapComparison, numXSnaps As Long, numYSnaps As Long @@ -376,7 +376,7 @@ Private Sub SnapRectByMoving(ByRef srcRectF As RectF, ByRef dstRectF As RectF) 'Determine the minimum snap distance required for this zoom value. Dim snapThreshold As Double - snapThreshold = Tools_Move.GetSnapDistance() * (1# / Zoom.GetZoomRatioFromIndex(PDImages.GetActiveImage.ImgViewport.GetZoomIndex)) + snapThreshold = Tools_Move.GetSnap_Distance() * (1# / Zoom.GetZoomRatioFromIndex(PDImages.GetActiveImage.ImgViewport.GetZoomIndex)) 'If the minimum value falls beneath the minimum snap distance, snap away! If (minDistX < snapThreshold) Then @@ -406,7 +406,7 @@ Private Function GetSnapTargets_X(ByRef dstSnaps() As SnapComparison) As Long GetSnapTargets_X = 0 'Canvas edges first - If Tools_Move.GetSnapCanvasEdge() Then + If Tools_Move.GetSnap_CanvasEdge() Then 'Ensure at space is available in the target array If (UBound(dstSnaps) < GetSnapTargets_X + 1) Then ReDim Preserve dstSnaps(0 To GetSnapTargets_X * 2 - 1) As SnapComparison @@ -429,7 +429,7 @@ Private Function GetSnapTargets_Y(ByRef dstSnaps() As SnapComparison) As Long GetSnapTargets_Y = 0 'Canvas edges first - If Tools_Move.GetSnapCanvasEdge() Then + If Tools_Move.GetSnap_CanvasEdge() Then 'Ensure at space is available in the target array If (UBound(dstSnaps) < GetSnapTargets_Y + 1) Then ReDim Preserve dstSnaps(0 To GetSnapTargets_Y * 2 - 1) As SnapComparison @@ -463,7 +463,7 @@ Public Sub TransformCurrentLayer(ByVal curImageX As Double, ByVal curImageY As D ' the mouse pointer (e.g. the layer edges, which are not located at the mouse position), so we'll need to wait ' to snap until the transform has been applied to the underlying layer. Dim srcPtF As PointFloat, snappedPtF As PointFloat - If Tools_Move.GetSnapCanvasEdge() Then + If Tools_Move.GetSnap_Any() Then Select Case m_CurPOI Case poi_CornerNW, poi_CornerNE, poi_CornerSW, poi_CornerSE diff --git a/Modules/UserPrefs.bas b/Modules/UserPrefs.bas index 57f5bfd58..aa1b574e1 100644 --- a/Modules/UserPrefs.bas +++ b/Modules/UserPrefs.bas @@ -609,8 +609,9 @@ Public Sub LoadUserSettings() Tools.SetToolSetting_HighResMouse UserPrefs.GetPref_Boolean("Tools", "HighResMouseInput", True) m_CanvasColor = Colors.GetRGBLongFromHex(UserPrefs.GetPref_String("Interface", "CanvasColor", "#a0a0a0")) + Interface.ToggleSnapOptions pdst_Global, True, UserPrefs.GetPref_Boolean("Interface", "snap-global", True) Interface.ToggleSnapOptions pdst_CanvasEdge, True, UserPrefs.GetPref_Boolean("Interface", "snap-canvas-edge", True) - Tools_Move.SetSnapDistance UserPrefs.GetPref_Long("Interface", "snap-distance", 8&) + Tools_Move.SetSnap_Distance UserPrefs.GetPref_Long("Interface", "snap-distance", 8&) 'Users can supply a (secret!) "UIFont" setting in the "Interface" segment if they ' want to override PD's default font object. diff --git a/PhotoDemon.vbp b/PhotoDemon.vbp index 7e38f8724..f01a0989c 100644 --- a/PhotoDemon.vbp +++ b/PhotoDemon.vbp @@ -526,7 +526,7 @@ Description="PhotoDemon Photo Editor" CompatibleMode="0" MajorVer=9 MinorVer=1 -RevisionVer=350 +RevisionVer=352 AutoIncrementVer=1 ServerSupportFiles=0 VersionComments="Copyright 2000-2024 Tanner Helland - photodemon.org"