Need testing of Show Canvas(TAB) positioning

Use this in Ten Scripts. This script emulates the show canvas (when you press TAB)

Preferably Krita 5 (but probably 4 would work too, didn’t check)

Make sure you are on a constant zoom level. (like 100%, NOT Fit Page)

I need to know if:

  1. OS and version
  2. If you are on subwindow or tab document mode
  3. If it is working properly to place the canvas where it was left off
  4. Any flickering

Or any other issue.

Here is the script:

from krita import *

mode = int(Krita.instance().readSetting('','mdi_viewmode','0'))
hideTitle = Krita.instance().readSetting('','hideTitleBarFullscreen','true') == 'true'

#0 = sub Window
#1 = tabs

action = Krita.instance().action('view_show_canvas_only')
qwin = Krita.instance().activeWindow().qwindow()
mdi = qwin.findChild(QMdiArea)

subwins = {}
scrollArea = {}
hbar = {}
vbar = {}
startSubPos = {}
subMax={}


startPos = mdi.mapToGlobal(QPoint( 0, 0 ))

for subwin in mdi.subWindowList():
    view = subwin.widget()
    viewName = view.objectName()
    subwins[viewName] = subwin
    subMax[viewName]=subwin.isMaximized()
    scrollArea[viewName] = view.findChild(QAbstractScrollArea)
    hbar[viewName] = scrollArea[viewName].horizontalScrollBar()
    vbar[viewName] = scrollArea[viewName].verticalScrollBar()

    if mode == 0 and not subMax[viewName]:
        startSubPos[viewName] = subwin.mapToGlobal(QPoint())
    else:
        startSubPos[viewName] = QPoint(hbar[viewName].value(),vbar[viewName].value())

def restorePos():
    pos = mdi.mapToGlobal(QPoint())
    for viewName in subwins.keys():
        if mode == 0 and not subMax[viewName]:
            subwins[viewName].move(startSubPos[viewName] - pos)
        else:
            hbar[viewName].setValue(-startPos.x() + pos.x() + startSubPos[viewName].x() )
            vbar[viewName].setValue(-startPos.y() + pos.y() + startSubPos[viewName].y() )


class coverWidget(QWidget):
    def __init__(self, obj, parent=None):
        super().__init__(parent)
        s = qApp.screenAt(qwin.pos()) if hideTitle else qwin
        
        r = s.geometry()
        self.img = obj.grab()
        objPos = obj.mapToGlobal(QPoint(0,0)) if hideTitle else obj.mapTo(qwin,QPoint(0,0))
        self.imgSize = QRect(objPos.x(), objPos.y(), obj.width(), obj.height())
        
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.ToolTip | Qt.WindowTransparentForInput)
        self.setFocusPolicy(Qt.NoFocus);
        self.setAttribute(Qt.WA_ShowWithoutActivating)
        
        self.setGeometry(r)
        self.setAutoFillBackground(True)
        self.setPalette(qwin.palette())

        self.timerId = 0
        
        
        
    def paintEvent(self, event):
        painter = QPainter()
        painter.begin(self)
        painter.drawPixmap(self.imgSize, self.img)
        painter.end()
        
    def timerEvent(self, event):
        self.killTimer(self.timerId)
        mdi.removeEventFilter(mdiFilter)
        self.hide()
    
    
    def animateHide(self, tms):
        self.timerId = self.startTimer(tms)
        

class mdiFilterClass(QWidget):
    def __init__(self, parent=None):
        super().__init__()

        
    def eventFilter(self, obj, event):
        ev = event.type()
        if ev == QEvent.Move:
            restorePos()
            
        return False

mdiFilter = mdiFilterClass()
mdi.installEventFilter(mdiFilter)                

csubw = mdi.currentSubWindow()
ovObj = csubw.findChild(QOpenGLWidget) if mode == 1 or csubw.isMaximized() else mdi

ovWin = coverWidget(ovObj, mdi)



ovWin.show()


action.trigger()
qApp.processEvents()


ovWin.animateHide(100)

Please try both version 2 as well:

5 Likes

I feel like subwindow/tab document mode is a workaround to a design goal that couldn’t be achieved.
Like, it could simply be detachable tabs which become floating windows if you detach them from the UI.
Hope someday this changes in the sense that it makes the choice between subwindow or tab modes obsolete in the settings menu. Just passing by.

It works perfect!!

OS and version = Windows 10, Krita-nightly-5.0.0-beta5-0e4b8448bf
If you are on subwindow or tab document mode = Subwindow mode
If it is working properly to place the canvas where it was left off =Yes, it works perfect
Any flickering = Nope, everything seems to work without problems.

1 Like

Really good job! :slight_smile:

  1. Kubuntu 20.04, Krita-5.0.0-beta3-cc9aa2f
  2. Subwindow
  3. Yes in general. But when one window is maximized, and the second one is floating over it in “stay on top” mode, the floater is being moved.
1 Like

Neat!

  1. macOS 10.13.6 krita-nightly_9b7cb19 | openSUSE Tumbleweed in VM Krita-5.0.0-beta5-cfb5f8a
  2. Tested both subwindow and tab
  3. Yes :+1:
  4. All elements on screen except canvas briefly flashes in krita window theme colour. (I have the ”Titlebar (hiding the titlebar will make Krita go full-screen)” options unchecked in Canvas-only settings)
1 Like

@SchrodingerCat @wojtryb @emilm

Thanks for checking!

Can you try 2 more versions? I am particularly interested if there are any improvements in flickering or experience.

Version 2 - 100ms
from krita import *

mode = int(Krita.instance().readSetting('','mdi_viewmode','0'))
hideTitle = Krita.instance().readSetting('','hideTitleBarFullscreen','true') == 'true'

#0 = sub Window
#1 = tabs

action = Krita.instance().action('view_show_canvas_only')
qwin = Krita.instance().activeWindow().qwindow()
mdi = qwin.findChild(QMdiArea)
csubw = mdi.currentSubWindow()


subwins = {}
scrollArea = {}
hbar = {}
vbar = {}
startSubPos = {}
subMax={}




startPos = mdi.mapToGlobal(QPoint( 0, 0 ))

for subwin in mdi.subWindowList():
    view = subwin.widget()
    viewName = view.objectName()
    subwins[viewName] = subwin
    subMax[viewName]=subwin.isMaximized()
    scrollArea[viewName] = view.findChild(QAbstractScrollArea)
    hbar[viewName] = scrollArea[viewName].horizontalScrollBar()
    vbar[viewName] = scrollArea[viewName].verticalScrollBar()

    if mode == 0 and not subMax[viewName]:
        startSubPos[viewName] = subwin.mapToGlobal(QPoint())
    else:
        startSubPos[viewName] = QPoint(hbar[viewName].value(),vbar[viewName].value())

def restorePos():
    pos = mdi.mapToGlobal(QPoint())
    for viewName in subwins.keys():
        if mode == 0 and not subMax[viewName]:
            subwins[viewName].move(startSubPos[viewName] - pos)
        else:
            hbar[viewName].setValue(-startPos.x() + pos.x() + startSubPos[viewName].x() )
            vbar[viewName].setValue(-startPos.y() + pos.y() + startSubPos[viewName].y() )


class coverWidget(QWidget):
    def __init__(self, obj, parent=None):
        super().__init__(parent)
        s = qApp.screenAt(qwin.pos()) if hideTitle else qwin
        
        r = s.geometry()
        self.img = obj.grab()
        objPos = obj.mapToGlobal(QPoint(0,0)) if hideTitle else obj.mapTo(qwin,QPoint(0,0))
        self.imgSize = QRect(objPos.x(), objPos.y(), obj.width(), obj.height())
        
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.ToolTip | Qt.WindowTransparentForInput)
        self.setFocusPolicy(Qt.NoFocus);
        self.setAttribute(Qt.WA_ShowWithoutActivating)
        
        self.setGeometry(r)
        self.setAutoFillBackground(True)
        self.setPalette(qwin.palette())

        self.showTimerId = 0
        self.hideTimerId = 0
        
        
        
    def paintEvent(self, event):
        painter = QPainter()
        painter.begin(self)
        painter.drawPixmap(self.imgSize, self.img)
        painter.end()
        
    def timerEvent(self, event):
        if event.timerId() == self.showTimerId:
            self.killTimer(self.showTimerId)
            action.trigger()
            qApp.processEvents()

            
            self.animateHide(100)
        else:
            self.killTimer(self.hideTimerId)
            mdi.removeEventFilter(mdiFilter)
            self.hide()
        
        
    def animateShow(self, tms):
        self.show()
        self.showTimerId = self.startTimer(tms)
    
    def animateHide(self, tms):
        self.hideTimerId = self.startTimer(tms)
        

class mdiFilterClass(QWidget):
    def __init__(self, parent=None):
        super().__init__()

        
    def eventFilter(self, obj, event):
        ev = event.type()
        if ev == QEvent.Move:
            restorePos()

            
        return False

mdiFilter = mdiFilterClass()
mdi.installEventFilter(mdiFilter)                


ovObj = csubw.findChild(QOpenGLWidget) if mode == 1 or csubw.isMaximized() else mdi


ovWin = coverWidget(ovObj, mdi)


ovWin.animateShow(100)
Version 2 - 200ms
from krita import *

mode = int(Krita.instance().readSetting('','mdi_viewmode','0'))
hideTitle = Krita.instance().readSetting('','hideTitleBarFullscreen','true') == 'true'

#0 = sub Window
#1 = tabs

action = Krita.instance().action('view_show_canvas_only')
qwin = Krita.instance().activeWindow().qwindow()
mdi = qwin.findChild(QMdiArea)
csubw = mdi.currentSubWindow()


subwins = {}
scrollArea = {}
hbar = {}
vbar = {}
startSubPos = {}
subMax={}




startPos = mdi.mapToGlobal(QPoint( 0, 0 ))

for subwin in mdi.subWindowList():
    view = subwin.widget()
    viewName = view.objectName()
    subwins[viewName] = subwin
    subMax[viewName]=subwin.isMaximized()
    scrollArea[viewName] = view.findChild(QAbstractScrollArea)
    hbar[viewName] = scrollArea[viewName].horizontalScrollBar()
    vbar[viewName] = scrollArea[viewName].verticalScrollBar()

    if mode == 0 and not subMax[viewName]:
        startSubPos[viewName] = subwin.mapToGlobal(QPoint())
    else:
        startSubPos[viewName] = QPoint(hbar[viewName].value(),vbar[viewName].value())

def restorePos():
    pos = mdi.mapToGlobal(QPoint())
    for viewName in subwins.keys():
        if mode == 0 and not subMax[viewName]:
            subwins[viewName].move(startSubPos[viewName] - pos)
        else:
            hbar[viewName].setValue(-startPos.x() + pos.x() + startSubPos[viewName].x() )
            vbar[viewName].setValue(-startPos.y() + pos.y() + startSubPos[viewName].y() )


class coverWidget(QWidget):
    def __init__(self, obj, parent=None):
        super().__init__(parent)
        s = qApp.screenAt(qwin.pos()) if hideTitle else qwin
        
        r = s.geometry()
        self.img = obj.grab()
        objPos = obj.mapToGlobal(QPoint(0,0)) if hideTitle else obj.mapTo(qwin,QPoint(0,0))
        self.imgSize = QRect(objPos.x(), objPos.y(), obj.width(), obj.height())
        
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.ToolTip | Qt.WindowTransparentForInput)
        self.setFocusPolicy(Qt.NoFocus);
        self.setAttribute(Qt.WA_ShowWithoutActivating)
        
        self.setGeometry(r)
        self.setAutoFillBackground(True)
        self.setPalette(qwin.palette())

        self.showTimerId = 0
        self.hideTimerId = 0
        
        
        
    def paintEvent(self, event):
        painter = QPainter()
        painter.begin(self)
        painter.drawPixmap(self.imgSize, self.img)
        painter.end()
        
    def timerEvent(self, event):
        if event.timerId() == self.showTimerId:
            self.killTimer(self.showTimerId)
            action.trigger()
            qApp.processEvents()

            
            self.animateHide(200)
        else:
            self.killTimer(self.hideTimerId)
            mdi.removeEventFilter(mdiFilter)
            self.hide()
        
        
    def animateShow(self, tms):
        self.show()
        self.showTimerId = self.startTimer(tms)
    
    def animateHide(self, tms):
        self.hideTimerId = self.startTimer(tms)
        

class mdiFilterClass(QWidget):
    def __init__(self, parent=None):
        super().__init__()

        
    def eventFilter(self, obj, event):
        ev = event.type()
        if ev == QEvent.Move:
            restorePos()

            
        return False

mdiFilter = mdiFilterClass()
mdi.installEventFilter(mdiFilter)                


ovObj = csubw.findChild(QOpenGLWidget) if mode == 1 or csubw.isMaximized() else mdi


ovWin = coverWidget(ovObj, mdi)


ovWin.animateShow(200)

How do they compare?

@wojtryb - Try version 1B below for the max subwindow fix

@emilm - Yeah, I didn’t add that option yet. Can you test with it enabled? (I want to be sure it works fine full screen mode)

Also, here is version 1 fixed with titlebar unchecked:

Version 1B - Titlebar fix and Max size fix
from krita import *

mode = int(Krita.instance().readSetting('','mdi_viewmode','0'))
hideTitle = Krita.instance().readSetting('','hideTitleBarFullscreen','true') == 'true'

#0 = sub Window
#1 = tabs

action = Krita.instance().action('view_show_canvas_only')
qwin = Krita.instance().activeWindow().qwindow()
mdi = qwin.findChild(QMdiArea)

subwins = {}
scrollArea = {}
hbar = {}
vbar = {}
startSubPos = {}
subMax={}


startPos = mdi.mapToGlobal(QPoint( 0, 0 ))

for subwin in mdi.subWindowList():
    view = subwin.widget()
    viewName = view.objectName()
    subwins[viewName] = subwin
    subMax[viewName]=subwin.isMaximized()
    scrollArea[viewName] = view.findChild(QAbstractScrollArea)
    hbar[viewName] = scrollArea[viewName].horizontalScrollBar()
    vbar[viewName] = scrollArea[viewName].verticalScrollBar()

    if mode == 0 and not subMax[viewName]:
        startSubPos[viewName] = subwin.mapToGlobal(QPoint())
    else:
        startSubPos[viewName] = QPoint(hbar[viewName].value(),vbar[viewName].value())

def restorePos():
    pos = mdi.mapToGlobal(QPoint())
    for viewName in subwins.keys():
        if mode == 0 and not subMax[viewName]:
            subwins[viewName].move(startSubPos[viewName] - pos)
        else:
            hbar[viewName].setValue(-startPos.x() + pos.x() + startSubPos[viewName].x() )
            vbar[viewName].setValue(-startPos.y() + pos.y() + startSubPos[viewName].y() )


class coverWidget(QWidget):
    def __init__(self, obj, parent=None):
        super().__init__(parent)
        s = qApp.screenAt(qwin.pos()) if hideTitle else qwin
        
        r = s.geometry()
        self.img = obj.grab()
        objPos = obj.mapToGlobal(QPoint(0,0)) if hideTitle else obj.mapTo(qwin,QPoint(0,0))
        self.imgSize = QRect(objPos.x(), objPos.y(), obj.width(), obj.height())
        
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.ToolTip | Qt.WindowTransparentForInput)
        self.setFocusPolicy(Qt.NoFocus);
        self.setAttribute(Qt.WA_ShowWithoutActivating)
        
        self.setGeometry(r)
        self.setAutoFillBackground(True)
        self.setPalette(qwin.palette())

        self.timerId = 0
        
        
        
    def paintEvent(self, event):
        painter = QPainter()
        painter.begin(self)
        painter.drawPixmap(self.imgSize, self.img)
        painter.end()
        
    def timerEvent(self, event):
        self.killTimer(self.timerId)
        mdi.removeEventFilter(mdiFilter)
        self.hide()
    
    
    def animateHide(self, tms):
        self.timerId = self.startTimer(tms)
        

class mdiFilterClass(QWidget):
    def __init__(self, parent=None):
        super().__init__()

        
    def eventFilter(self, obj, event):
        ev = event.type()
        if ev == QEvent.Move:
            restorePos()
            
        return False

mdiFilter = mdiFilterClass()
mdi.installEventFilter(mdiFilter)                

csubw = mdi.currentSubWindow()
ovObj = csubw.findChild(QOpenGLWidget) if mode == 1 or csubw.isMaximized() else mdi

ovWin = coverWidget(ovObj, mdi)



ovWin.show()


action.trigger()
qApp.processEvents()


ovWin.animateHide(100)
1 Like

Looks good, I prefer the faster one. :slightly_smiling_face:

The window changing size after exiting full screen is not a bug of your script but happens when exiting canvas only mode the regular way as well.
I also tried setting animation speed to 0. Is there a reason not to?

macOS 10.13.6 krita-nightly_9b7cb19
(Tried in 4.4.8 as well and works fine but throws a dialog box saying that the script was executed. ^^ )

1 Like

Hi, it works fine for me! I tried Version 2 -100 ms.

Linux Mint 20.2 Cinnamon.
Krita 5 beta 5
tab mode

The position seems to return exactly where it should.
There’s a flicker when the script is run : On my 4k monitor the canvas area and its gray backdrop drops down for a short bit:


On my HD monitor, the canvas seems to move to the right a little.

If I’m reading this script right, it is trying to blank the screen to cover this flicker? If so, that doesn’t seem to work for me.

1 Like

@emilm - The reason for the timer thing is to hide flickering and jumpy transition effects so it looks cleaner. I am guessing in the 1st test you are just testing maximized and 2nd test you are testing already full screened? Cause I don’t see any transition effects on your 2nd, but 1st there is?

Letting the user choose how they want it is in consideration, but the problem is that it would have to be a hidden option until 5.1? Due to the string freeze.

Here is another test to try which is 200ms, but makes it feel more snappy:

Version 3
from krita import *

mode = int(Krita.instance().readSetting('','mdi_viewmode','0'))
hideTitle = Krita.instance().readSetting('','hideTitleBarFullscreen','true') == 'true'
canvasColor = Krita.instance().readSetting('','canvasBorderColor','128,128,128')

#0 = sub Window
#1 = tabs

action = Krita.instance().action('view_show_canvas_only')
qwin = Krita.instance().activeWindow().qwindow()
mdi = qwin.findChild(QMdiArea)
csubw = mdi.currentSubWindow()


subwins = {}
scrollArea = {}
hbar = {}
vbar = {}
startSubPos = {}
subMax={}
mdiPal = True



startPos = mdi.mapToGlobal(QPoint( 0, 0 ))

for subwin in mdi.subWindowList():
    view = subwin.widget()
    viewName = view.objectName()
    subwins[viewName] = subwin
    subMax[viewName]=subwin.isMaximized()
    scrollArea[viewName] = view.findChild(QAbstractScrollArea)
    hbar[viewName] = scrollArea[viewName].horizontalScrollBar()
    vbar[viewName] = scrollArea[viewName].verticalScrollBar()

    if mode == 0 and not subMax[viewName]:
        startSubPos[viewName] = subwin.mapToGlobal(QPoint())
    else:
        startSubPos[viewName] = QPoint(hbar[viewName].value(),vbar[viewName].value())
        mdiPal = False

def restorePos():
    pos = mdi.mapToGlobal(QPoint())
    for viewName in subwins.keys():
        if mode == 0 and not subMax[viewName]:
            subwins[viewName].move(startSubPos[viewName] - pos)
        else:
            hbar[viewName].setValue(-startPos.x() + pos.x() + startSubPos[viewName].x() )
            vbar[viewName].setValue(-startPos.y() + pos.y() + startSubPos[viewName].y() )

            


class coverWidget(QWidget):
    def __init__(self, obj, parent=None):
        super().__init__(parent)
        s = qApp.screenAt(qwin.pos()) if hideTitle else qwin
        
        self.obj = obj
        r = s.geometry()
        self.img = obj.grab()
        objPos = obj.mapToGlobal(QPoint(0,0)) if hideTitle else obj.mapTo(qwin,QPoint(0,0))
        self.imgSize = QRect(objPos.x(), objPos.y(), obj.width(), obj.height())
        
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.ToolTip | Qt.WindowTransparentForInput)
        self.setFocusPolicy(Qt.NoFocus);
        self.setAttribute(Qt.WA_ShowWithoutActivating)
        
        self.setGeometry(r)
        self.setAutoFillBackground(True)
        

        self.showTimerId = 0
        self.hideTimerId = 0
        self.paintTimerId = 0
        
        
        
    def paintEvent(self, event):
        painter = QPainter()
        painter.begin(self)
        painter.drawPixmap(self.imgSize, self.img)
        painter.end()
        
    def timerEvent(self, event):
        if event.timerId() == self.showTimerId:
            self.killTimer(self.showTimerId)
            action.trigger()
            qApp.processEvents(QEventLoop.ExcludeUserInputEvents)

            
            self.animateHide(200)
        elif event.timerId() == self.paintTimerId:
            obj = self.obj
            objPos = obj.mapToGlobal(QPoint(0,0)) if hideTitle else obj.mapTo(qwin,QPoint(0,0))
            self.imgSize = QRect(objPos.x(), objPos.y(), obj.width(), obj.height())
            self.img = obj.grab()
            self.update()
        else:
            self.killTimer(self.paintTimerId)
            self.killTimer(self.hideTimerId)
            mdi.removeEventFilter(mdiFilter)
            self.hide()
        
        
    def animateShow(self, tms):
        pal = QPalette()
        pal.setColor(QPalette.Window, mdi.background().color() if mdiPal else QColor(*map(int, canvasColor.split(','))))
        self.setPalette(pal)
        
        self.show()
        self.showTimerId = self.startTimer(tms)
        self.paintTimerId = self.startTimer(50)
    
    def animateHide(self, tms):
        self.hideTimerId = self.startTimer(tms)
        

class mdiFilterClass(QWidget):
    def __init__(self, parent=None):
        super().__init__()

        
    def eventFilter(self, obj, event):
        ev = event.type()
        if ev == QEvent.Move:
            restorePos()
            

            
        return False

mdiFilter = mdiFilterClass()
mdi.installEventFilter(mdiFilter)                



ovObj = csubw.findChild(QOpenGLWidget) if mode == 1 or csubw.isMaximized() else mdi



ovWin = coverWidget(ovObj, mdi)


ovWin.animateShow(200)

@hulmanen - What about the 200ms version? (or try version 3, it also has better background overlay)

Also, can you clarify on what you mean move to the right? Is it a temporary move due to transistion? or is it putting it in the wrong spot?

I tested in the linux VM again and now see the animation as well as the flickering on the first version or when set to 0 ms.

First test ”hide titlebar” option on, then ”hide titlebar” option off. Yeah, no animation (and flickering) on macOS other than the OS fullscreen window transition. (Could be because old macOS version?)

The version 3 does feel more responsive than version 2 at 200 ms, though I might prefer the version 2 at 100 ms (or v2 at 0 ms on macOS).

Either way, I think whatever you choose will be fine. :+1:

1 Like

So it seems on mac it isn’t eliminating the canvas temporary jumping due to full screen animation like it is on the linux. But regardless there is no flicker correct?

I don’t know if new or old mac would make a difference since I don’t have a mac to test on. I’m on linux.

I will most likely go with letting the user pick what they want. With maybe some defaults on each platform. But with 5.0 so close, and the approach being a bit hacky (Thus needing testing). It would probably would have to wait till 5.0.1, and at least 5.1 for people to configure it via GUI.

In the meantime, I will probably publish it as a plugin so people can test all kinds of options in real life scenarios so that it can safely be merged into Krita without issues.

2 Likes

Thanks everyone again for testing!

It seems that it is unlikely to make it in by Krita 5 as it is just too close to release to get enough testing. In the meantime, I packaged it up as a plugin for use for Krita 5. Please test it out if you have the chance preferably in real life testing (AFTER you make sure everything is kosher in general testing, don’t risk your work). And post if everything seems fine, if there are issues and what timeout you think works best for your platform as the default. If testing ends up well, it will be pushed to Krita 5.1 alpha.

3 Likes