def test_loader_compressed(test_pz_filename): """Tests for loading .pz files and the supports_compressed flag.""" class TestLoader: extensions = ["test"] @staticmethod def load_file(path, options, record=None): return ModelRoot("loaded") # Test with property absent with registered_type(TestLoader): model = Loader.get_global_ptr().load_sync( test_pz_filename, LoaderOptions(LoaderOptions.LF_no_cache)) assert model is None # Test with property False, should give same result TestLoader.supports_compressed = False with registered_type(TestLoader): model = Loader.get_global_ptr().load_sync( test_pz_filename, LoaderOptions(LoaderOptions.LF_no_cache)) assert model is None # Test with property True, should work TestLoader.supports_compressed = True with registered_type(TestLoader): model = Loader.get_global_ptr().load_sync( test_pz_filename, LoaderOptions(LoaderOptions.LF_no_cache)) assert model is not None assert model.name == "loaded" # Test with property invalid type, should not register TestLoader.supports_compressed = None with pytest.raises(TypeError): LoaderFileTypeRegistry.get_global_ptr().register_type(TestLoader)
def test_loader_ram_cache(test_filename): """Tests that the Python loader plug-ins write to the RAM cache.""" # Ensure a clean slate. from panda3d.core import ModelPool ModelPool.release_all_models() with registered_type(DummyLoader): model1 = Loader.get_global_ptr().load_sync( test_filename, LoaderOptions(LoaderOptions.LF_no_disk_cache | LoaderOptions.LF_allow_instance)) assert model1 is not None assert model1.name == "loaded" assert ModelPool.has_model(test_filename) assert ModelPool.get_model(test_filename, True) == model1 model2 = Loader.get_global_ptr().load_sync( test_filename, LoaderOptions(LoaderOptions.LF_cache_only | LoaderOptions.LF_allow_instance)) assert model2 is not None assert model1 == model2 ModelPool.release_model(model2)
def startModelLoadingAsync(self): """ NOTE: this seems to invoke a few bugs (crashes, sporadic model reading errors, etc) so is disabled for now... """ self.monitorNP = None self.keyboardNP = None self.loadingError = False # force the "loading" to take some time after the first run... options = LoaderOptions() options.setFlags(options.getFlags() | LoaderOptions.LFNoCache) def gotMonitorModel(model): if not model: self.loadingError = True self.monitorNP = model self.loader.loadModel("monitor", loaderOptions=options, callback=gotMonitorModel) def gotKeyboardModel(model): if not model: self.loadingError = True self.keyboardNP = model self.loader.loadModel("takeyga_kb", loaderOptions=options, callback=gotKeyboardModel)
def test_loader_success(test_filename): """Tests that a normal dummy loader successfully loads.""" with registered_type(DummyLoader): model = Loader.get_global_ptr().load_sync( test_filename, LoaderOptions(LoaderOptions.LF_no_cache)) assert model is not None assert model.name == "loaded"
def loadModel(modelPath): loader = Loader.getGlobalPtr() loaderOptions = LoaderOptions() node = loader.loadSync(Filename(modelPath), loaderOptions) if node is not None: nodePath = NodePath(node) nodePath.setTag('model-filename', os.path.abspath(modelPath)) else: raise IOError('Could not load model file: %s' % (modelPath)) return nodePath
def loadModel(modelPath): loader = Loader.getGlobalPtr() # NOTE: disable disk and RAM caching to avoid filling memory when loading multiple scenes loaderOptions = LoaderOptions(LoaderOptions.LF_no_cache) node = loader.loadSync(Filename(modelPath), loaderOptions) if node is not None: nodePath = NodePath(node) nodePath.setTag('model-filename', os.path.abspath(modelPath)) else: raise IOError('Could not load model file: %s' % (modelPath)) return nodePath
def test_loader_extensions(test_filename): """Tests multi-extension loaders.""" class MultiExtensionLoader: extensions = ["test1", "teSt2"] @staticmethod def load_file(path, options, record=None): return ModelRoot("loaded") fp1 = tempfile.NamedTemporaryFile(suffix='.test1', delete=False) fp1.write(b"test1") fp1.close() fn1 = Filename.from_os_specific(fp1.name) fn1.make_true_case() fp2 = tempfile.NamedTemporaryFile(suffix='.TEST2', delete=False) fp2.write(b"test2") fp2.close() fn2 = Filename.from_os_specific(fp2.name) fn2.make_true_case() try: with registered_type(MultiExtensionLoader): model1 = Loader.get_global_ptr().load_sync( fn1, LoaderOptions(LoaderOptions.LF_no_cache)) assert model1 is not None assert model1.name == "loaded" model2 = Loader.get_global_ptr().load_sync( fn2, LoaderOptions(LoaderOptions.LF_no_cache)) assert model2 is not None assert model2.name == "loaded" finally: os.unlink(fp1.name) os.unlink(fp2.name) # Ensure that both were unregistered. registry = LoaderFileTypeRegistry.get_global_ptr() assert not registry.get_type_from_extension("test1") assert not registry.get_type_from_extension("test2")
def test_loader_exception(test_filename): """Tests for a loader that raises an exception.""" class FailingLoader: extensions = ["test"] @staticmethod def load_file(path, options, record=None): raise Exception("test error") with registered_type(FailingLoader): model = Loader.get_global_ptr().load_sync( test_filename, LoaderOptions(LoaderOptions.LF_no_cache)) assert model is None
def loadModel(self, modelPath, priority=None): self.notify.debug('Loading model... ' + modelPath) request = self.loader.makeAsyncRequest( Filename(modelPath), LoaderOptions(LoaderOptions.LFSearch | LoaderOptions.LFReportErrors | LoaderOptions.LFNoCache)) if priority is not None: request.setPriority(priority) request.setDoneEvent(self.asyncRequestDoneEvent) request.setPythonObject(modelPath) self.requests[modelPath] = (request, self.loadModelCallback, [modelPath]) self.loader.loadAsync(request)
def test_loader_nonexistent(): """Verifies that non-existent files fail before calling load_file.""" flag = [False] class AssertiveLoader: extensions = ["test"] @staticmethod def load_file(path, options, record=None): flag[0] = True assert False, "should never get here" with registered_type(AssertiveLoader): model = Loader.get_global_ptr().load_sync( "/non-existent", LoaderOptions(LoaderOptions.LF_no_cache)) assert model is None assert not flag[0]
def loadModel(self, modelPath, loaderOptions=None, noCache=None, allowInstance=False, okMissing=None, callback=None, extraArgs=[], priority=None): """ Attempts to load a model or models from one or more relative pathnames. If the input modelPath is a string (a single model pathname), the return value will be a NodePath to the model loaded if the load was successful, or None otherwise. If the input modelPath is a list of pathnames, the return value will be a list of NodePaths and/or Nones. loaderOptions may optionally be passed in to control details about the way the model is searched and loaded. See the LoaderOptions class for more. The default is to look in the ModelPool (RAM) cache first, and return a copy from that if the model can be found there. If the bam cache is enabled (via the model-cache-dir config variable), then that will be consulted next, and if both caches fail, the file will be loaded from disk. If noCache is True, then neither cache will be consulted or updated. If allowInstance is True, a shared instance may be returned from the ModelPool. This is dangerous, since it is easy to accidentally modify the shared instance, and invalidate future load attempts of the same model. Normally, you should leave allowInstance set to False, which will always return a unique copy. If okMissing is True, None is returned if the model is not found or cannot be read, and no error message is printed. Otherwise, an IOError is raised if the model is not found or cannot be read (similar to attempting to open a nonexistent file). (If modelPath is a list of filenames, then IOError is raised if *any* of the models could not be loaded.) If callback is not None, then the model load will be performed asynchronously. In this case, loadModel() will initiate a background load and return immediately. The return value will be an object that may later be passed to loader.cancelRequest() to cancel the asynchronous request. At some later point, when the requested model(s) have finished loading, the callback function will be invoked with the n loaded models passed as its parameter list. It is possible that the callback will be invoked immediately, even before loadModel() returns. If you use callback, you may also specify a priority, which specifies the relative importance over this model over all of the other asynchronous load requests (higher numbers are loaded first). True asynchronous model loading requires Panda to have been compiled with threading support enabled (you can test Thread.isThreadingSupported()). In the absence of threading support, the asynchronous interface still exists and still behaves exactly as described, except that loadModel() might not return immediately. """ assert Loader.notify.debug("Loading model: %s" % (modelPath)) if loaderOptions == None: loaderOptions = LoaderOptions() else: loaderOptions = LoaderOptions(loaderOptions) if okMissing is not None: if okMissing: loaderOptions.setFlags(loaderOptions.getFlags() & ~LoaderOptions.LFReportErrors) else: loaderOptions.setFlags(loaderOptions.getFlags() | LoaderOptions.LFReportErrors) else: okMissing = ((loaderOptions.getFlags() & LoaderOptions.LFReportErrors) == 0) if noCache is not None: if noCache: loaderOptions.setFlags(loaderOptions.getFlags() | LoaderOptions.LFNoCache) else: loaderOptions.setFlags(loaderOptions.getFlags() & ~LoaderOptions.LFNoCache) if allowInstance: loaderOptions.setFlags(loaderOptions.getFlags() | LoaderOptions.LFAllowInstance) if isinstance(modelPath, types.StringTypes) or \ isinstance(modelPath, Filename): # We were given a single model pathname. modelList = [modelPath] gotList = False else: # Assume we were given a list of model pathnames. modelList = modelPath gotList = True if callback is None: # We got no callback, so it's a synchronous load. result = [] for modelPath in modelList: node = self.loader.loadSync(Filename(modelPath), loaderOptions) if (node != None): nodePath = NodePath(node) else: nodePath = None result.append(nodePath) if not okMissing and None in result: message = 'Could not load model file(s): %s' % (modelList, ) raise IOError, message if gotList: return result else: return result[0] else: # We got a callback, so we want an asynchronous (threaded) # load. We'll return immediately, but when all of the # requested models have been loaded, we'll invoke the # callback (passing it the models on the parameter list). cb = Loader.Callback(len(modelList), gotList, callback, extraArgs) i = 0 for modelPath in modelList: request = self.loader.makeAsyncRequest(Filename(modelPath), loaderOptions) if priority is not None: request.setPriority(priority) request.setDoneEvent(self.hook) request.setPythonObject((cb, i)) i += 1 self.loader.loadAsync(request) cb.requests[request] = True return cb
def loadModel(self, modelPath, loaderOptions = None, noCache = None, allowInstance = False, okMissing = None, callback = None, extraArgs = [], priority = None): """ Attempts to load a model or models from one or more relative pathnames. If the input modelPath is a string (a single model pathname), the return value will be a NodePath to the model loaded if the load was successful, or None otherwise. If the input modelPath is a list of pathnames, the return value will be a list of NodePaths and/or Nones. loaderOptions may optionally be passed in to control details about the way the model is searched and loaded. See the LoaderOptions class for more. The default is to look in the ModelPool (RAM) cache first, and return a copy from that if the model can be found there. If the bam cache is enabled (via the model-cache-dir config variable), then that will be consulted next, and if both caches fail, the file will be loaded from disk. If noCache is True, then neither cache will be consulted or updated. If allowInstance is True, a shared instance may be returned from the ModelPool. This is dangerous, since it is easy to accidentally modify the shared instance, and invalidate future load attempts of the same model. Normally, you should leave allowInstance set to False, which will always return a unique copy. If okMissing is True, None is returned if the model is not found or cannot be read, and no error message is printed. Otherwise, an IOError is raised if the model is not found or cannot be read (similar to attempting to open a nonexistent file). (If modelPath is a list of filenames, then IOError is raised if *any* of the models could not be loaded.) If callback is not None, then the model load will be performed asynchronously. In this case, loadModel() will initiate a background load and return immediately. The return value will be an object that may later be passed to loader.cancelRequest() to cancel the asynchronous request. At some later point, when the requested model(s) have finished loading, the callback function will be invoked with the n loaded models passed as its parameter list. It is possible that the callback will be invoked immediately, even before loadModel() returns. If you use callback, you may also specify a priority, which specifies the relative importance over this model over all of the other asynchronous load requests (higher numbers are loaded first). True asynchronous model loading requires Panda to have been compiled with threading support enabled (you can test Thread.isThreadingSupported()). In the absence of threading support, the asynchronous interface still exists and still behaves exactly as described, except that loadModel() might not return immediately. """ assert Loader.notify.debug("Loading model: %s" % (modelPath)) if loaderOptions == None: loaderOptions = LoaderOptions() else: loaderOptions = LoaderOptions(loaderOptions) if okMissing is not None: if okMissing: loaderOptions.setFlags(loaderOptions.getFlags() & ~LoaderOptions.LFReportErrors) else: loaderOptions.setFlags(loaderOptions.getFlags() | LoaderOptions.LFReportErrors) else: okMissing = ((loaderOptions.getFlags() & LoaderOptions.LFReportErrors) == 0) if noCache is not None: if noCache: loaderOptions.setFlags(loaderOptions.getFlags() | LoaderOptions.LFNoCache) else: loaderOptions.setFlags(loaderOptions.getFlags() & ~LoaderOptions.LFNoCache) if allowInstance: loaderOptions.setFlags(loaderOptions.getFlags() | LoaderOptions.LFAllowInstance) if isinstance(modelPath, types.StringTypes) or \ isinstance(modelPath, Filename): # We were given a single model pathname. modelList = [modelPath] gotList = False else: # Assume we were given a list of model pathnames. modelList = modelPath gotList = True if callback is None: # We got no callback, so it's a synchronous load. result = [] for modelPath in modelList: node = self.loader.loadSync(Filename(modelPath), loaderOptions) if (node != None): nodePath = NodePath(node) else: nodePath = None result.append(nodePath) if not okMissing and None in result: message = 'Could not load model file(s): %s' % (modelList,) raise IOError, message if gotList: return result else: return result[0] else: # We got a callback, so we want an asynchronous (threaded) # load. We'll return immediately, but when all of the # requested models have been loaded, we'll invoke the # callback (passing it the models on the parameter list). cb = Loader.Callback(len(modelList), gotList, callback, extraArgs) i=0 for modelPath in modelList: request = self.loader.makeAsyncRequest(Filename(modelPath), loaderOptions) if priority is not None: request.setPriority(priority) request.setDoneEvent(self.hook) request.setPythonObject((cb, i)) i+=1 self.loader.loadAsync(request) cb.requests[request] = True return cb
def __init__(self, command, fileBrowser=False, defaultPath="~", defaultFilename="unnamed.txt", fileExtensions=[], tooltip=None, iconDir=None, parent=None, theme=None): """ A simple file and folder browser command: The command that will be called on closing the browser fileBrowser: If set to True the browser will show files, otherwise it will only show folders defaultPath: The initial path the browser will be set to show defaultFilename: The filename that will be set by default, only usefull if fileBrowser is True fileExtensions: A list of extensions. Only files with those extensions will be shown. Only usefull if fileBrowser is True tooltip: An instance of the Tooltip class to display tooltips for certain parts of the editor iconDir: A directory path that contains replacement images. It must contain all required images which are: File.png Folder.png FolderNew.png FolderShowHidden.png FolderUp.png Reload.png parent: Another DirectGUI element which has pixel2d as root parent. The browser frame is placed centered so a frame for example should have equal sizes in horizontal and vertical directions e.g. frameSize=(-250,250,-200,200) """ self.theme = theme if theme is not None else LightTheme() self.tt = tooltip self.command = command self.showFiles = fileBrowser self.fileExtensions = fileExtensions self.showHidden = False self.parent = parent self.imageOpts = LoaderOptions() self.imageOpts.set_auto_texture_scale(ATS_none) if self.theme.icon_dir is not None: self.iconDir = self.theme.icon_dir elif iconDir is None: fn = Filename.fromOsSpecific(os.path.dirname(__file__)) fn.makeTrueCase() self.iconDir = str(fn) + "/icons" else: self.iconDir = iconDir self.selectedViewType = "Symbol" self.currentPath = os.path.expanduser(defaultPath) if not os.path.exists(self.currentPath): self.currentPath = os.path.expanduser("~") self.previousPath = self.currentPath if self.parent is None: self.parent = base.pixel2d self.screenWidthPx = base.getSize()[0] self.screenHeightPx = base.getSize()[1] self.position = LPoint3f(base.getSize()[0] / 2, 0, -base.getSize()[1] / 2) else: self.screenWidthPx = self.parent.getWidth() self.screenHeightPx = self.parent.getHeight() self.position = LPoint3f(0) self.screenWidthPxHalf = self.screenWidthPx * 0.5 self.screenHeightPxHalf = self.screenHeightPx * 0.5 self.mainFrame = DirectFrame( relief=1, frameSize=(-self.screenWidthPxHalf, self.screenWidthPxHalf, -self.screenHeightPxHalf, self.screenHeightPxHalf), frameColor=self.theme.main_background, pos=self.position, parent=self.parent, state=DGG.NORMAL, ) self.pathRightMargin = 155 # NOTE: Add 28 for each button to the right + 15px margin self.pathEntryWidth = self.screenWidthPx - self.pathRightMargin - 28 # The path entry on top of the window self.pathEntry = DirectEntry( text_fg=self.theme.default_text_color, parent=self.mainFrame, relief=DGG.SUNKEN, frameColor=self.theme.entry_background, pad=(0.2, 0.2), pos=LPoint3f(-self.screenWidthPxHalf + 15, 0, self.screenHeightPxHalf - 25), scale=12, width=self.pathEntryWidth / 12, overflow=True, command=self.entryAccept, initialText=self.currentPath, focusInCommand=base.messenger.send, focusInExtraArgs=["unregisterKeyboardEvents"], focusOutCommand=base.messenger.send, focusOutExtraArgs=["reregisterKeyboardEvents"], ) # ---------------- # CONTROL BUTTONS # ---------------- x = self.screenWidthPxHalf - self.pathRightMargin + 18 # RELOAD self.btnReload = DirectButton( parent=self.mainFrame, relief=1, frameColor=self.theme.icon_button_background, frameSize=(-14, 14, -10, 18), pos=LPoint3f(x, 0, self.screenHeightPxHalf - 25), command=self.folderReload, image=loader.load_texture(f"{self.iconDir}/Reload.png", loaderOptions=self.imageOpts), image_scale=14, image_pos=(0, 0, 4), ) self.btnReload.setTransparency(TransparencyAttrib.M_multisample) if self.tt is not None: self.btnReload.bind(DGG.ENTER, self.tt.show, ["Reload Folder"]) self.btnReload.bind(DGG.EXIT, self.tt.hide) # MOVE UP ONE FOLDER x += 28 self.btnFolderUp = DirectButton( parent=self.mainFrame, relief=1, frameColor=self.theme.icon_button_background, frameSize=(-14, 14, -10, 18), pos=LPoint3f(x, 0, self.screenHeightPxHalf - 25), command=self.folderUp, image=loader.load_texture(f"{self.iconDir}/FolderUp.png", loaderOptions=self.imageOpts), image_scale=14, image_pos=(0, 0, 4), ) self.btnFolderUp.setTransparency(TransparencyAttrib.M_multisample) if self.tt is not None: self.btnFolderUp.bind(DGG.ENTER, self.tt.show, ["Move up one level"]) self.btnFolderUp.bind(DGG.EXIT, self.tt.hide) # CREATE NEW FOLDER x += 28 self.btnFolderNew = DirectButton( parent=self.mainFrame, relief=1, frameColor=self.theme.icon_button_background, frameSize=(-14, 14, -10, 18), pos=LPoint3f(x, 0, self.screenHeightPxHalf - 25), command=self.folderNew, image=loader.load_texture(f"{self.iconDir}/FolderNew.png", loaderOptions=self.imageOpts), image_scale=14, image_pos=(0, 0, 4), ) self.btnFolderNew.setTransparency(TransparencyAttrib.M_multisample) if self.tt is not None: self.btnFolderNew.bind(DGG.ENTER, self.tt.show, ["Create new folder"]) self.btnFolderNew.bind(DGG.EXIT, self.tt.hide) # SHOW HIDDEN FOLDERS x += 28 self.btnFolderShowHidden = DirectButton( parent=self.mainFrame, relief=1, frameColor=self.theme.icon_button_background, frameSize=(-14, 14, -10, 18), pos=LPoint3f(x, 0, self.screenHeightPxHalf - 25), command=self.folderShowHidden, image=loader.load_texture(f"{self.iconDir}/FolderShowHidden.png", loaderOptions=self.imageOpts), image_scale=14, image_pos=(0, 0, 4), ) self.btnFolderShowHidden.setTransparency( TransparencyAttrib.M_multisample) if self.tt is not None: self.btnFolderShowHidden.bind( DGG.ENTER, self.tt.show, ["Show/Hide hidden files and folders"]) self.btnFolderShowHidden.bind(DGG.EXIT, self.tt.hide) # TOGGLE VIEW TYPE x += 28 self.btnViewType = DirectButton( parent=self.mainFrame, relief=1, frameColor=self.theme.icon_button_background, frameSize=(-14, 14, -10, 18), pos=LPoint3f(x, 0, self.screenHeightPxHalf - 25), command=self.toggleViewType, image=loader.load_texture(f"{self.iconDir}/ViewTypeSymbol.png", loaderOptions=self.imageOpts), image_scale=14, image_pos=(0, 0, 4), ) self.btnViewType.setTransparency(TransparencyAttrib.M_multisample) if self.tt is not None: self.btnViewType.bind( DGG.ENTER, self.tt.show, ["Toggle view between Symbols and Detail list"]) self.btnViewType.bind(DGG.EXIT, self.tt.hide) # -------------- # CONTENT FRAME # -------------- color = self.theme.scrollbar_controlls_color self.container = DirectScrolledFrame( relief=DGG.RIDGE, borderWidth=(2, 2), frameColor=self.theme.main_background, frameSize=(-self.screenWidthPxHalf + 10, self.screenWidthPxHalf - 10, -self.screenHeightPxHalf + 50, self.screenHeightPxHalf - 50), canvasSize=(-self.screenWidthPxHalf + 31, self.screenWidthPxHalf - 10, -self.screenHeightPxHalf + 50, self.screenHeightPxHalf - 50), pos=LPoint3f(0, 0, 0), parent=self.mainFrame, scrollBarWidth=20, verticalScroll_scrollSize=20, verticalScroll_thumb_relief=DGG.FLAT, verticalScroll_incButton_relief=DGG.FLAT, verticalScroll_decButton_relief=DGG.FLAT, verticalScroll_thumb_frameColor=color, verticalScroll_incButton_frameColor=color, verticalScroll_decButton_frameColor=color, verticalScroll_frameColor=self.theme.scroll_background, horizontalScroll_thumb_relief=DGG.FLAT, horizontalScroll_incButton_relief=DGG.FLAT, horizontalScroll_decButton_relief=DGG.FLAT, horizontalScroll_thumb_frameColor=color, horizontalScroll_incButton_frameColor=color, horizontalScroll_decButton_frameColor=color, horizontalScroll_frameColor=self.theme.scroll_background, state=DGG.NORMAL, ) self.container.bind(DGG.MWDOWN, self.scroll, [0.01]) self.container.bind(DGG.MWUP, self.scroll, [-0.01]) # ACCEPT BUTTON self.btnOk = DirectButton( parent=self.mainFrame, relief=1, frameColor=self.theme.text_button_background, frameSize=(-45, 45, -6, 14), pos=LPoint3f(self.screenWidthPxHalf - 160, 0, -self.screenHeightPxHalf + 25), text="ok", text_scale=12, text_fg=self.theme.default_text_color, command=command, extraArgs=[1], ) # CANCEL BUTTON self.btnCancel = DirectButton( parent=self.mainFrame, relief=1, frameColor=self.theme.text_button_background, frameSize=(-45, 45, -6, 14), pos=LPoint3f(self.screenWidthPxHalf - 55, 0, -self.screenHeightPxHalf + 25), text="Cancel", text_scale=12, text_fg=self.theme.default_text_color, command=command, extraArgs=[0]) # SELECTED FILE ENTRY FIELD if self.showFiles: self.txtFileName = DirectEntry( text_fg=self.theme.default_text_color, parent=self.mainFrame, relief=DGG.SUNKEN, frameColor=self.theme.entry_background, pad=(0.2, 0.2), pos=LPoint3f(-self.screenWidthPxHalf + 25, 0, -self.screenHeightPxHalf + 25), scale=12, width=200 / 12, overflow=True, command=self.filenameAccept, initialText=defaultFilename, focusInCommand=base.messenger.send, focusInExtraArgs=["unregisterKeyboardEvents"], focusOutCommand=base.messenger.send, focusOutExtraArgs=["reregisterKeyboardEvents"], ) # ------------------ # CREATE NEW FOLDER # ------------------ # FRAME FOR CREATING NEW FOLDER self.newFolderFrame = DirectFrame( parent=self.mainFrame, relief=1, frameSize=(-self.screenWidthPxHalf + 10, self.screenWidthPxHalf - 10, -20, 20), pos=LPoint3f(0, 0, self.screenHeightPxHalf - 55), frameColor=self.theme.popup_frame_background, ) # LABEL FOR NEW FOLDER NAME ENTRY self.txtNewFolderName = DirectLabel( parent=self.newFolderFrame, text="New Folder Name", text_scale=12, text_fg=self.theme.default_text_color, frameColor=(0, 0, 0, 0), text_align=TextNode.ALeft, pos=(-self.screenWidthPxHalf + 15, 0, -3), ) # ENTRY FOR THE NEW FOLDER NAME self.folderName = DirectEntry( text_fg=self.theme.default_text_color, parent=self.newFolderFrame, relief=DGG.SUNKEN, frameColor=self.theme.entry_background, pad=(0.2, 0.2), pos=LPoint3f( -self.screenWidthPxHalf + 25 + self.txtNewFolderName.getWidth(), 0, -4), scale=12, width=((self.screenWidthPxHalf - 25) * 2 - self.txtNewFolderName.getWidth() - 100) / 12, overflow=True, command=self.entryAccept, initialText="New Folder", focusInCommand=base.messenger.send, focusInExtraArgs=["unregisterKeyboardEvents"], focusOutCommand=base.messenger.send, focusOutExtraArgs=["reregisterKeyboardEvents"], ) # ACCEPT BUTTON FOR THE CREATE NEW FOLDER self.btnCreate = DirectButton( parent=self.newFolderFrame, relief=1, frameColor=self.theme.text_button_background, frameSize=(-45, 45, -6, 14), pos=LPoint3f(self.screenWidthPxHalf - 65, 0, -4), text="Create", text_scale=12, text_fg=self.theme.default_text_color, command=self.folderCreate, extraArgs=[0]) # Hide the create new folder frame by default self.newFolderFrame.hide() # --------------- # UPDATE CONTENT # --------------- # Initial loading of the files and folders of the current path self.folderReload() # handle window resizing self.prevScreenSize = base.getSize() if self.parent is base.pixel2d: self.accept("window-event", self.windowEventHandler)