📄 widget.py
字号:
if self.state is not True: self.set_state(True) else: return key def mouse_event(self, (maxcol,), event, button, x, y, focus): """Set state to True on button 1 press.""" if button != 1 or not is_mouse_press(event): return False if self.state is not True: self.set_state(True) return True class Button(WidgetWrap): button_left = Text("<") button_right = Text(">") def selectable(self): return True def __init__(self, label, on_press=None, user_data=None): """ label -- markup for button label on_press -- callback function for button "press" on_press( button object, user_data=None) user_data -- additional param for on_press callback, ommited if None for compatibility reasons """ self.__super.__init__(None) # self.w set by set_label below self.set_label( label ) self.on_press = on_press self.user_data = user_data def set_label(self, label): self.label = label self.w = Columns([ ('fixed', 1, self.button_left), Text( label ), ('fixed', 1, self.button_right)], dividechars=1) self._invalidate() def get_label(self): return self.label def render(self, (maxcol,), focus=False): """Display button. Show a cursor when in focus.""" canv = self.__super.render((maxcol,), focus=focus) canv = CompositeCanvas(canv) if focus and maxcol >2: canv.cursor = (2,0) return canv def get_cursor_coords(self, (maxcol,)): """Return the location of the cursor.""" if maxcol >2: return (2,0) return None def keypress(self, (maxcol,), key): """Call on_press on spage or enter.""" if key not in (' ','enter'): return key if self.on_press: if self.user_data is None: self.on_press(self) else: self.on_press(self, self.user_data) def mouse_event(self, (maxcol,), event, button, x, y, focus): """Call on_press on button 1 press.""" if button != 1 or not is_mouse_press(event): return False if self.on_press: self.on_press( self ) return True return False class GridFlow(FlowWidget): def selectable(self): """Return True if the cell in focus is selectable.""" return self.focus_cell and self.focus_cell.selectable() def __init__(self, cells, cell_width, h_sep, v_sep, align): """ cells -- list of flow widgets to display cell_width -- column width for each cell h_sep -- blank columns between each cell horizontally v_sep -- blank rows between cells vertically (if more than one row is required to display all the cells) align -- horizontal alignment of cells, see "align" parameter of Padding widget for available options """ self.__super.__init__() self.cells = cells self.cell_width = cell_width self.h_sep = h_sep self.v_sep = v_sep self.align = align self.focus_cell = None if cells: self.focus_cell = cells[0] self._cache_maxcol = None def set_focus(self, cell): """Set the cell in focus. cell -- widget or integer index into self.cells""" if type(cell) == type(0): assert cell>=0 and cell<len(self.cells) self.focus_cell = self.cells[cell] else: assert cell in self.cells self.focus_cell = cell self._cache_maxcol = None self._invalidate() def get_display_widget(self, (maxcol,)): """ Arrange the cells into columns (and possibly a pile) for display, input or to calculate rows. """ # use cache if possible if self._cache_maxcol == maxcol: return self._cache_display_widget self._cache_maxcol = maxcol self._cache_display_widget = self.generate_display_widget( (maxcol,)) return self._cache_display_widget def generate_display_widget(self, (maxcol,)): """ Actually generate display widget (ignoring cache) """ d = Divider() if len(self.cells) == 0: # how dull return d if self.v_sep > 1: # increase size of divider d.top = self.v_sep-1 # cells per row bpr = (maxcol+self.h_sep) / (self.cell_width+self.h_sep) if bpr == 0: # too narrow, pile them on top of eachother l = [self.cells[0]] f = 0 for b in self.cells[1:]: if b is self.focus_cell: f = len(l) if self.v_sep: l.append(d) l.append(b) return Pile(l, f) if bpr >= len(self.cells): # all fit on one row k = len(self.cells) f = self.cells.index(self.focus_cell) cols = Columns(self.cells, self.h_sep, f) rwidth = (self.cell_width+self.h_sep)*k - self.h_sep row = Padding(cols, self.align, rwidth) return row out = [] s = 0 f = 0 while s < len(self.cells): if out and self.v_sep: out.append(d) k = min( len(self.cells), s+bpr ) cells = self.cells[s:k] if self.focus_cell in cells: f = len(out) fcol = cells.index(self.focus_cell) cols = Columns(cells, self.h_sep, fcol) else: cols = Columns(cells, self.h_sep) rwidth = (self.cell_width+self.h_sep)*(k-s)-self.h_sep row = Padding(cols, self.align, rwidth) out.append(row) s += bpr return Pile(out, f) def _set_focus_from_display_widget(self, w): """Set the focus to the item in focus in the display widget.""" if isinstance(w, Padding): # unwrap padding w = w.w w = w.get_focus() if w in self.cells: self.set_focus(w) return if isinstance(w, Padding): # unwrap padding w = w.w w = w.get_focus() #assert w == self.cells[0], `w, self.cells` self.set_focus(w) def keypress(self, (maxcol,), key): """ Pass keypress to display widget for handling. Capture focus changes.""" d = self.get_display_widget((maxcol,)) if not d.selectable(): return key key = d.keypress( (maxcol,), key) if key is None: self._set_focus_from_display_widget(d) return key def rows(self, (maxcol,), focus=False): """Return rows used by this widget.""" d = self.get_display_widget((maxcol,)) return d.rows((maxcol,), focus=focus) def render(self, (maxcol,), focus=False ): """Use display widget to render.""" d = self.get_display_widget((maxcol,)) return d.render((maxcol,), focus) def get_cursor_coords(self, (maxcol,)): """Get cursor from display widget.""" d = self.get_display_widget((maxcol,)) if not d.selectable(): return None return d.get_cursor_coords((maxcol,)) def move_cursor_to_coords(self, (maxcol,), col, row ): """Set the widget in focus based on the col + row.""" d = self.get_display_widget((maxcol,)) if not d.selectable(): # happy is the default return True r = d.move_cursor_to_coords((maxcol,), col, row) if not r: return False self._set_focus_from_display_widget(d) self._invalidate() return True def mouse_event(self, (maxcol,), event, button, col, row, focus): """Send mouse event to contained widget.""" d = self.get_display_widget((maxcol,)) r = d.mouse_event( (maxcol,), event, button, col, row, focus ) if not r: return False self._set_focus_from_display_widget(d) self._invalidate() return True def get_pref_col(self, (maxcol,)): """Return pref col from display widget.""" d = self.get_display_widget((maxcol,)) if not d.selectable(): return None return d.get_pref_col((maxcol,)) class PaddingError(Exception): passclass Padding(Widget): def __init__(self, w, align, width, min_width=None): """ w -- a box, flow or fixed widget to pad on the left and/or right align -- one of: 'left', 'center', 'right' ('fixed left', columns) ('fixed right', columns) ('relative', percentage 0=left 100=right) width -- one of: number of columns wide ('fixed right', columns) Only if align is 'fixed left' ('fixed left', columns) Only if align is 'fixed right' ('relative', percentage of total width) None to enable clipping mode min_width -- the minimum number of columns for w or None Padding widgets will try to satisfy width argument first by reducing the align amount when necessary. If width still cannot be satisfied it will also be reduced. Clipping Mode: In clipping mode w is treated as a fixed widget and this widget expects to be treated as a flow widget. w will be clipped to fit within the space given. For example, if align is 'left' then w may be clipped on the right. """ self.__super.__init__() at,aa,wt,wa=decompose_align_width(align, width, PaddingError) self.w = w self.align_type, self.align_amount = at, aa self.width_type, self.width_amount = wt, wa self.min_width = min_width def render(self, size, focus=False): left, right = self.padding_values(size, focus) maxcol = size[0] maxcol -= left+right if self.width_type is None: canv = self.w.render((), focus) else: canv = self.w.render((maxcol,)+size[1:], focus) if canv.cols() == 0: canv = SolidCanvas(' ', size[0], canv.rows()) canv = CompositeCanvas(canv) canv.set_depends([self.w]) return canv canv = CompositeCanvas(canv) canv.set_depends([self.w]) if left != 0 or right != 0: canv.pad_trim_left_right(left, right) return canv def padding_values(self, size, focus): """Return the number of columns to pad on the left and right. Override this method to define custom padding behaviour.""" maxcol = size[0] if self.width_type is None: width, ignore = self.w.pack(focus=focus) return calculate_padding(self.align_type, self.align_amount, 'fixed', width, None, maxcol, clip=True ) return calculate_padding( self.align_type, self.align_amount, self.width_type, self.width_amount, self.min_width, maxcol ) def selectable(self): """Return the selectable value of self.w.""" return self.w.selectable() def rows(self, (maxcol,), focus=False ): """Return the rows needed for self.w.""" if self.width_type is None: ignore, height = self.w.pack(focus) return height left, right = self.padding_values((maxcol,), focus) return self.w.rows( (maxcol-left-right,), focus=focus ) def keypress(self, size, key): """Pass keypress to self.w.""" maxcol = size[0] left, right = self.padding_values(size, True) maxvals = (maxcol-left-right,)+size[1:] return self.w.keypress(maxvals, key) def get_cursor_coords(self,size): """Return the (x,y) coordinates of cursor within self.w.""" if not hasattr(self.w,'get_cursor_coords'): return None left, right = self.padding_values(size, True) maxcol = size[0] maxvals = (maxcol-left-right,)+size[1:] coords = self.w.get_cursor_coords(maxvals) if coords is None: return None x, y = coords return x+left, y def move_cursor_to_coords(self, size, x, y): """Set the cursor position with (x,y) coordinates of self.w. Returns True if move succeeded, False otherwise. """ if not hasattr(self.w,'move_cursor_to_coords'): return True left, right = self.padding_values(size, True) maxcol = size[0] maxvals = (maxcol-left-right,)+size[1:] if type(x)==type(0): if x < left: x = left elif x >= maxcol-right: x = maxcol-right-1 x -= left return self.w.move_cursor_to_coords(maxvals, x, y) def mouse_event(self, size, event, button, x, y, focus): """Send mouse event if position is within self.w.""" if not hasattr(self.w,'mouse_event'): return False left, right = self.padding_values(size, focus) maxcol = size[0] if x < left or x >= maxcol-right: return False maxvals = (maxcol-left-right,)+size[1:] return self.w.mouse_event(maxvals, event, button, x-left, y, focus) def get_pref_col(self, size): """Return the preferred column from self.w, or None.""" if not hasattr(self.w,'get_pref_col'): return None left, right = self.padding_values(size, True) maxcol = size[0] maxvals = (maxcol-left-right,)+size[1:] x = self.w.get_pref_col(maxvals) if type(x) == type(0): return x+left return x class FillerError(Exception): passclass Filler(BoxWidget): def __init__(self, body, valign="middle", height=None, min_height=None): """ body -- a flow widget or box widget to be filled around valign -- one of: 'top', 'middle', 'bottom' ('fixed top', rows) ('fixed bottom', rows) ('relative', percentage 0=top 100=bottom) height -- one of: None if body is a flow widget number of rows high ('fixed bottom', rows) Only if valign is 'fixed top' ('fixed top', rows) Only if valign is 'fixed bottom' ('relative', percentage of total height) min_height -- one of: None if no minimum or if body is a flow widget minimum number of rows for the widget when height not fixed If body is a flow widget then height and min_height must be set to None. Filler widgets will try to satisfy height argument first by reducing the valign amount when necessary. If height still cannot be satisfied it will also be reduced. """ self.__super.__init__() vt,va,ht,ha=decompose_valign_height(valign,height,FillerError) self._body = body self.valign_type, self.valign_amount = vt, va self.height_type, self.height_amount = ht, ha if self.height_type not in ('fixed', None): self.min_height = min_height else: self.min_height = None def get_body(self): return self._body def set_body(self, body):
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -