diff --git a/Forms/MainWindow.frm b/Forms/MainWindow.frm index 5926dba6f..0dc73d9f1 100644 --- a/Forms/MainWindow.frm +++ b/Forms/MainWindow.frm @@ -1729,6 +1729,14 @@ Begin VB.Form FormMain Caption = "Show status bar" Index = 7 End + Begin VB.Menu MnuView + Caption = "-" + Index = 8 + End + Begin VB.Menu MnuView + Caption = "Snap to canvas edges" + Index = 9 + End End Begin VB.Menu MnuWindowTop Caption = "Window" @@ -3828,6 +3836,10 @@ Private Sub MnuView_Click(Index As Integer) Actions.LaunchAction_ByName "view_rulers" Case 7 Actions.LaunchAction_ByName "view_statusbar" + Case 8 + '(separator) + Case 9 + Actions.LaunchAction_ByName "snap_canvasedge" End Select End Sub diff --git a/Forms/Tools_Options.frm b/Forms/Tools_Options.frm index 3279ab46c..0aca45f75 100644 --- a/Forms/Tools_Options.frm +++ b/Forms/Tools_Options.frm @@ -53,10 +53,23 @@ Begin VB.Form FormOptions Width = 8295 _ExtentX = 14631 _ExtentY = 11853 + Begin PhotoDemon.pdSpinner spnSnapDistance + Height = 375 + Left = 120 + TabIndex = 44 + Top = 4560 + Width = 1935 + _ExtentX = 3413 + _ExtentY = 661 + DefaultValue = 8 + Min = 1 + Max = 255 + Value = 8 + End Begin PhotoDemon.pdPictureBox picGrid Height = 735 Left = 150 - Top = 4530 + Top = 5610 Width = 735 _ExtentX = 1296 _ExtentY = 1296 @@ -133,7 +146,7 @@ Begin VB.Form FormOptions Height = 810 Left = 1080 TabIndex = 2 - Top = 4500 + Top = 5580 Width = 3015 _ExtentX = 5318 _ExtentY = 1429 @@ -144,7 +157,7 @@ Begin VB.Form FormOptions Height = 795 Left = 4140 TabIndex = 4 - Top = 4500 + Top = 5580 Width = 3015 _ExtentX = 5318 _ExtentY = 1402 @@ -155,7 +168,7 @@ Begin VB.Form FormOptions Height = 690 Left = 7260 TabIndex = 5 - Top = 4560 + Top = 5640 Width = 465 _ExtentX = 820 _ExtentY = 1217 @@ -165,7 +178,7 @@ Begin VB.Form FormOptions Height = 690 Left = 7770 TabIndex = 6 - Top = 4560 + Top = 5640 Width = 465 _ExtentX = 820 _ExtentY = 1217 @@ -175,7 +188,7 @@ Begin VB.Form FormOptions Height = 285 Index = 2 Left = 0 - Top = 4080 + Top = 5160 Width = 8205 _ExtentX = 14473 _ExtentY = 503 @@ -205,6 +218,18 @@ Begin VB.Form FormOptions ForeColor = 4210752 Layout = 2 End + Begin PhotoDemon.pdLabel lblTitle + Height = 285 + Index = 23 + Left = 0 + Top = 4080 + Width = 8100 + _ExtentX = 14288 + _ExtentY = 503 + Caption = "snap distance (in pixels)" + FontSize = 12 + ForeColor = 4210752 + End End Begin PhotoDemon.pdContainer picContainer Height = 6720 @@ -1180,10 +1205,12 @@ Private Sub cmdBarMini_OKClick() g_RecentMacros.MRU_NotifyNewMaxLimit End If + UserPrefs.SetPref_Long "Interface", "snap-distance", spnSnapDistance.Value + Tools_Move.SetSnapDistance spnSnapDistance.Value + UserPrefs.SetPref_Long "Transparency", "Alpha Check Mode", CLng(cboAlphaCheck.ListIndex) UserPrefs.SetPref_Long "Transparency", "Alpha Check One", CLng(csAlphaOne.Color) UserPrefs.SetPref_Long "Transparency", "Alpha Check Two", CLng(csAlphaTwo.Color) - UserPrefs.SetPref_Long "Transparency", "Alpha Check Size", cboAlphaCheckSize.ListIndex Drawing.CreateAlphaCheckerboardDIB g_CheckerboardPattern @@ -1408,6 +1435,7 @@ Private Sub LoadAllPreferences() csCanvasColor.Color = UserPrefs.GetCanvasColor() tudRecentFiles.Value = UserPrefs.GetPref_Long("Interface", "Recent Files Limit", 10) btsMRUStyle.ListIndex = UserPrefs.GetPref_Long("Interface", "MRU Caption Length", 0) + spnSnapDistance.Value = UserPrefs.GetPref_Long("Interface", "snap-distance", 8&) m_userInitiatedAlphaSelection = False cboAlphaCheck.ListIndex = UserPrefs.GetPref_Long("Transparency", "Alpha Check Mode", 0) csAlphaOne.Color = UserPrefs.GetPref_Long("Transparency", "Alpha Check One", RGB(255, 255, 255)) diff --git a/Modules/Actions.bas b/Modules/Actions.bas index 94b676b52..a4cfb1492 100644 --- a/Modules/Actions.bas +++ b/Modules/Actions.bas @@ -1299,6 +1299,7 @@ Private Function Launch_ByName_MenuView(ByRef srcMenuName As String, Optional By If (Not PDImages.IsImageActive()) Then Exit Function Dim cmdFound As Boolean: cmdFound = True + Dim newState As Boolean Select Case srcMenuName @@ -1344,16 +1345,17 @@ Private Function Launch_ByName_MenuView(ByRef srcMenuName As String, Optional By If FormMain.MainCanvas(0).IsZoomEnabled Then FormMain.MainCanvas(0).SetZoomDropDownIndex 21 Case "view_rulers" - Dim newRulerState As Boolean - newRulerState = Not FormMain.MainCanvas(0).GetRulerVisibility() - FormMain.MnuView(6).Checked = newRulerState - FormMain.MainCanvas(0).SetRulerVisibility newRulerState + newState = Not FormMain.MainCanvas(0).GetRulerVisibility() + FormMain.MnuView(6).Checked = newState + FormMain.MainCanvas(0).SetRulerVisibility newState Case "view_statusbar" - Dim newStatusBarState As Boolean - newStatusBarState = Not FormMain.MainCanvas(0).GetStatusBarVisibility() - FormMain.MnuView(7).Checked = newStatusBarState - FormMain.MainCanvas(0).SetStatusBarVisibility newStatusBarState + newState = Not FormMain.MainCanvas(0).GetStatusBarVisibility() + FormMain.MnuView(7).Checked = newState + FormMain.MainCanvas(0).SetStatusBarVisibility newState + + Case "snap_canvasedge" + Interface.ToggleSnapOptions pdst_CanvasEdge Case Else cmdFound = False @@ -2000,6 +2002,7 @@ Public Sub BuildActionDatabase() AddAction "zoom_1_16", vbNullString AddAction "view_rulers", vbNullString AddAction "view_statusbar", vbNullString + AddAction "snap_canvasedge", vbNullString 'AddAction "window_toolbox" AddAction "window_displaytoolbox", vbNullString AddAction "window_displaytoolcategories", vbNullString diff --git a/Modules/Interface.bas b/Modules/Interface.bas index 11c304e27..20448afdc 100644 --- a/Modules/Interface.bas +++ b/Modules/Interface.bas @@ -1210,6 +1210,30 @@ Public Sub ToggleImageTabstripVisibility(ByVal newSetting As Long, Optional ByVa End Sub +'Toggle one of the "snap to..." settings in the View menu. +' 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_CanvasEdge + idxTarget = IDX_BASE + 0 + If (Not forceInsteadOfToggle) Then newState = Not Tools_Move.GetSnapCanvasEdge() + Tools_Move.SetSnapCanvasEdge newState + UserPrefs.SetPref_Boolean "Interface", "snap-canvas-edge", newState + End Select + + 'Update the target menu state + FormMain.MnuView(idxTarget).Checked = newState + +End Sub + Public Function FixDPI(ByVal pxMeasurement As Long) As Long 'The first time this function is called, m_DPIRatio will be 0. Calculate it. diff --git a/Modules/Menus.bas b/Modules/Menus.bas index 4fb895e1f..0e03bd910 100644 --- a/Modules/Menus.bas +++ b/Modules/Menus.bas @@ -590,6 +590,8 @@ Public Sub InitializeMenus() AddMenuItem "-", "-", 8, 5 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 'Window Menu AddMenuItem "Window", "window_top", 9 diff --git a/Modules/MoveTool.bas b/Modules/MoveTool.bas index c662ec47b..de8cc2275 100644 --- a/Modules/MoveTool.bas +++ b/Modules/MoveTool.bas @@ -3,13 +3,14 @@ Attribute VB_Name = "Tools_Move" 'PhotoDemon Move/Size Tool Manager 'Copyright 2014-2024 by Tanner Helland 'Created: 24/May/14 -'Last updated: 22/December/22 -'Last update: add some trivial key-handling bits for the Hand tool (which is a different tool, but it has -' so few features that it's easier to just condense things here) +'Last updated: 05/April/24 +'Last update: start wiring up Snap capabilities ' 'This module interfaces between the layer move/size UI and actual layer backend. Look in the relevant ' tool panel form for more details on how the UI relays relevant tool data here. ' +'As of 2024, This module also handles move-related duties like snapping to various features. +' 'Unless otherwise noted, all source code in this file is shared under a simplified BSD license. ' Full license details are available in the LICENSE.md file, or at https://photodemon.org/license/ ' @@ -17,10 +18,19 @@ Attribute VB_Name = "Tools_Move" Option Explicit +Public Enum PD_SnapTargets + pdst_CanvasEdge +End Enum + +#If False Then + Private Const pdst_CanvasEdge = 0 +#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 '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. @@ -315,6 +325,19 @@ Public Function GetDrawLayerRotateNodes() As Boolean GetDrawLayerRotateNodes = m_DrawRotateNodes End Function +Public Function GetSnapCanvasEdge() As Boolean + GetSnapCanvasEdge = m_SnapToCanvasEdge +End Function + +Public Function GetSnapDistance() As Long + + GetSnapDistance = m_SnapDistance + + 'Failsafe only; should never trigger + If (GetSnapDistance < 1) Then GetSnapDistance = 8 + +End Function + Public Sub SetDrawLayerBorders(ByVal newState As Boolean) m_DrawLayerBorders = newState End Sub @@ -327,6 +350,16 @@ Public Sub SetDrawLayerRotateNodes(ByVal newState As Boolean) m_DrawRotateNodes = newState End Sub +Public Sub SetSnapCanvasEdge(ByVal newState As Boolean) + m_SnapToCanvasEdge = newState +End Sub + +Public Sub SetSnapDistance(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 + 'Relay functions for move selected pixels behavior Public Function GetMoveSelectedPixels_DefaultCut() As Boolean GetMoveSelectedPixels_DefaultCut = m_SelectionDefaultCut diff --git a/Modules/UserPrefs.bas b/Modules/UserPrefs.bas index 0c0d9755f..57f5bfd58 100644 --- a/Modules/UserPrefs.bas +++ b/Modules/UserPrefs.bas @@ -5,7 +5,7 @@ Attribute VB_Name = "UserPrefs" 'Created: 03/November/12 'Last updated: 21/February/22 'Last update: revert nightly builds to default to "nightly build" update track (I've gotten much better -' at disciplined nightly build development, and they are far more stable than the used to be). +' at disciplined nightly build development, and they are far more stable than they used to be). ' 'This is the modern incarnation of PD's old "INI file" module. It is responsible for managing all ' persistent user settings. @@ -609,6 +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_CanvasEdge, True, UserPrefs.GetPref_Boolean("Interface", "snap-canvas-edge", True) + Tools_Move.SetSnapDistance 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. m_UIFontName = UserPrefs.GetPref_String("Interface", "UIFont", vbNullString, False) diff --git a/PhotoDemon.vbp b/PhotoDemon.vbp index 877cd1506..22e80acc4 100644 --- a/PhotoDemon.vbp +++ b/PhotoDemon.vbp @@ -526,7 +526,7 @@ Description="PhotoDemon Photo Editor" CompatibleMode="0" MajorVer=9 MinorVer=1 -RevisionVer=347 +RevisionVer=348 AutoIncrementVer=1 ServerSupportFiles=0 VersionComments="Copyright 2000-2024 Tanner Helland - photodemon.org"