diff --git a/ViewModels/GridViewModel.fs b/ViewModels/GridViewModel.fs index 0132ff4..706e8f8 100644 --- a/ViewModels/GridViewModel.fs +++ b/ViewModels/GridViewModel.fs @@ -12,8 +12,8 @@ open Avalonia.Media open FSharp.Control.Reactive open System -open System.Collections.ObjectModel open model +open System.Collections.Generic #nowarn "0025" @@ -37,7 +37,7 @@ and GridViewModel(_gridid: int, ?_parent: GridViewModel, ?_gridsize: GridSize) a let m_cursor_vm = new CursorViewModel(None) let m_popupmenu_vm = new PopupMenuViewModel() - let m_child_grids = ObservableCollection() + let m_child_grids = ResizeArray() let m_resize_ev = Event() let m_input_ev = Event() let m_ext_winclose_ev = Event() @@ -65,9 +65,10 @@ and GridViewModel(_gridid: int, ?_parent: GridViewModel, ?_gridsize: GridSize) a let mutable m_anchor_col = 0 let mutable m_hidden = false let mutable m_is_external = false + let mutable m_is_float = false + let mutable m_z = -100 let mutable m_winid = 0 // for single-purpose windows e.g. floats and exts - let raiseInputEvent id e = m_input_ev.Trigger(id, e) let getPos (p: Point) = @@ -137,7 +138,7 @@ and GridViewModel(_gridid: int, ?_parent: GridViewModel, ?_gridsize: GridSize) a let markAllDirty () = m_griddirty <- true for c in m_child_grids do - c.MarkAllDirty() + c.MarkDirty() let rec markDirty ({ row = row; col = col; height = h; width = w } as dirty) = if h > 1 then @@ -302,10 +303,21 @@ and GridViewModel(_gridid: int, ?_parent: GridViewModel, ?_gridsize: GridSize) a let setMouse (en:bool) = m_mouse_en <- en + let getRootGrid() = + let mutable p = m_parent + let mutable q = this + while p.IsSome do + q <- p.Value + p <- p.Value.Parent + q + let closeGrid() = trace _gridid "closeGrid" if m_is_external then m_ext_winclose_ev.Trigger() + elif m_is_float then + m_hidden <- true + getRootGrid().MarkDirty() let setWinPos startrow startcol r c f = let oldRegion = { row = m_anchor_row; col = m_anchor_col; height = m_gridsize.rows; width = m_gridsize.cols} @@ -324,6 +336,13 @@ and GridViewModel(_gridid: int, ?_parent: GridViewModel, ?_gridsize: GridSize) a this.Focusable <- f parent.OnChildChanged oldRegion newRegion + let setWinFloatPos win anchor anchor_grid r c f z = + m_winid <- win + m_is_float <- true + m_z <- z + trace _gridid "setWinFloatPos: z = %d" z + setWinPos (int r) (int c) m_gridsize.rows m_gridsize.cols f // XXX assume assume NW + let hidePopupMenu() = m_popupmenu_vm.Show <- false @@ -350,9 +369,7 @@ and GridViewModel(_gridid: int, ?_parent: GridViewModel, ?_gridsize: GridSize) a | WinPos(_, _, startrow, startcol, c, r) -> setWinPos startrow startcol r c true | WinHide(_) -> m_hidden <- true | MsgSetPos(_, row, scrolled, sep_char) -> setWinPos row 0 m_gridsize.rows m_gridsize.cols true - | WinFloatPos (_, win, anchor, anchor_grid, r, c, f, z) -> - m_winid <- win - setWinPos (int r + 1) (int c) m_gridsize.rows m_gridsize.cols f // XXX assume attaching to grid #1, assume NW + | WinFloatPos (_, win, anchor, anchor_grid, r, c, f, z) -> setWinFloatPos win anchor anchor_grid r c f z | PopupMenuShow(items, selected, row, col, grid) -> this.ShowPopupMenu grid items selected row col | PopupMenuSelect(selected) -> selectPopupMenuPassive selected | PopupMenuHide -> hidePopupMenu () @@ -456,8 +473,16 @@ and GridViewModel(_gridid: int, ?_parent: GridViewModel, ?_gridsize: GridSize) a member __.CreateChild id r c = trace _gridid "CreateChild: #%d" id let child = GridViewModel(id, this, {rows=r; cols=c}) - m_child_grids.Add child + m_child_grids.Add child |> ignore + child.ZIndex <- this.ZIndex + 1 child :> IGridUI + member __.AddChild c = + let c = c :?> GridViewModel + trace _gridid "AddChild: #%d" c.GridId + m_child_grids.Add c |> ignore + c.Parent <- (Some this) + c.ZIndex <- this.ZIndex + 1 + markAllDirty() member __.RemoveChild c = ignore <| m_child_grids.Remove (c:?>GridViewModel) markAllDirty() @@ -467,6 +492,8 @@ and GridViewModel(_gridid: int, ?_parent: GridViewModel, ?_gridsize: GridSize) a | Some p -> (p:>IGridUI).RemoveChild this m_parent <- None + m_z <- -100 + markAllDirty() member __.CursorGoto id row col = if m_parent.IsSome && id = _gridid then @@ -519,7 +546,7 @@ and GridViewModel(_gridid: int, ?_parent: GridViewModel, ?_gridsize: GridSize) a for c in m_child_grids do c.MarkClean() - member __.MarkAllDirty = markAllDirty + member __.MarkDirty = markAllDirty // converts grid position to UI Point member __.GetPoint row col = @@ -576,6 +603,12 @@ and GridViewModel(_gridid: int, ?_parent: GridViewModel, ?_gridsize: GridSize) a member __.Rows with get() = m_gridsize.rows member __.AnchorRow with get() = m_anchor_row member __.AnchorCol with get() = m_anchor_col + member __.AbsAnchor with get() = + match m_parent with + | Some p -> + let pr,pc = p.AbsAnchor + pr + m_anchor_row, pc + m_anchor_col + | _ -> m_anchor_row, m_anchor_col member __.Dirty with get() = m_griddirty member __.DrawOps with get() = m_drawops member __.Hidden with get():bool = m_hidden @@ -597,6 +630,9 @@ and GridViewModel(_gridid: int, ?_parent: GridViewModel, ?_gridsize: GridSize) a member __.Focusable with get() = m_gridfocusable and set(v) = ignore <| this.RaiseAndSetIfChanged(&m_gridfocusable, v) member __.ExtWinId = m_winid member __.ExtWinClosed = m_ext_winclose_ev.Publish + member __.Parent with get() = m_parent and set(v) = m_parent <- v + member __.ZIndex with get() = m_z and set(v) = m_z <- v + (******************* Events ***********************) diff --git a/Views/Grid.xaml.fs b/Views/Grid.xaml.fs index b46d2fd..6a94ca2 100644 --- a/Views/Grid.xaml.fs +++ b/Views/Grid.xaml.fs @@ -13,7 +13,6 @@ open Avalonia.Platform open Avalonia.Media.Imaging open Avalonia.VisualTree open System -open System.Collections.Specialized open FSharp.Control.Reactive open Avalonia.Data open Avalonia.Visuals.Media.Imaging @@ -29,6 +28,7 @@ open GridHelper open model open Avalonia.Input.TextInput open Avalonia.Input +open System.Collections.Generic type Grid() as this = inherit Canvas() @@ -113,7 +113,8 @@ type Grid() as this = | CharType.Emoji -> true | _ -> issym - let topLeft = grid_vm.GetPoint (row + vm.AnchorRow) (col + vm.AnchorCol) + let abs_r,abs_c = vm.AbsAnchor + let topLeft = grid_vm.GetPoint (row + abs_r) (col + abs_c) let bottomRight = topLeft + grid_vm.GetPoint 1 nr_col let bg_region = Rect(topLeft, bottomRight) @@ -245,26 +246,42 @@ type Grid() as this = // prevent repetitive drawings let _drawnRegions = ResizeArray() + let _drawVMs = ResizeArray() + + let rec scanDrawVMs (vm: GridViewModel) = + if vm.Hidden then () + else + _drawVMs.Add(vm) + vm.ChildGrids |> Seq.iter scanDrawVMs + + let drawOps (vm: GridViewModel) = + let abs_r,abs_c = vm.AbsAnchor + // if other grids tainted the region, mark it dirty + let touched = _drawnRegions + |> Seq.exists(fun(r,c,ce) -> + abs_r <= r && + r < abs_r + vm.Rows && + not( abs_c >= ce || c >= abs_c + vm.Cols )) + if touched then vm.MarkDirty() + + trace vm "drawOps: z=%d" vm.ZIndex - let rec drawOps (vm: GridViewModel) = if vm.Hidden then false elif vm.Dirty then trace vm "drawing whole grid" for row = 0 to vm.Rows - 1 do drawBufferLine vm grid_dc row 0 vm.Cols - for c in vm.ChildGrids do - c.MarkAllDirty() - drawOps c |> ignore + _drawnRegions.Add(row + abs_r, abs_c, vm.Cols + abs_c) true else + // not tainted. can draw with my draw ops. let draw row col colend = - let covered = _drawnRegions |> Seq.exists(fun (r, c, ce) -> r = row + vm.AnchorRow && c <= col + vm.AnchorCol && ce >= colend + vm.AnchorCol) + let covered = _drawnRegions |> Seq.exists(fun (r, c, ce) -> r = row + abs_r && c <= col + abs_c && ce >= colend + abs_c) if not covered then drawBufferLine vm grid_dc row col colend - _drawnRegions.Add(row + vm.AnchorRow, col + vm.AnchorCol, colend + vm.AnchorCol) + _drawnRegions.Add(row + abs_r, col + abs_c, colend + abs_c) else () - //trace vm "region %d %d %d waived" row col colend vm.DrawOps |> Seq.iter ( function @@ -274,55 +291,19 @@ type Grid() as this = else (top - row, left, bot, right) for row = t to b - 1 do draw row l r - - (*Note: the captured bitmap still misaligns with the rendered text*) - (*Until this is fixed, we have to draw the scrolled region line by line...*) - - (*let s_topLeft = grid_vm.GetPoint top left*) - (*let s_bottomRight = grid_vm.GetPoint (bot) (right)*) - (*let vec = grid_vm.GetPoint row 0*) - (*let src, dst = *) - (*if row > 0 then*) - (*Rect(s_topLeft + vec, s_bottomRight),*) - (*Rect(s_topLeft, s_bottomRight - vec)*) - (*else*) - (*Rect(s_topLeft, s_bottomRight + vec),*) - (*Rect(s_topLeft - vec, s_bottomRight)*) - - (*grid_dc_scroll.PushClip(dst)*) - (*grid_dc_scroll.Clear(Avalonia.Media.Colors.Transparent)*) - (*grid_dc_scroll.PopClip()*) - - (*use r1 = grid_fb.PlatformImpl.CloneAs<_>()*) - (*grid_dc_scroll.DrawBitmap(r1, 1.0, src, dst)*) - - (*grid_dc.PushClip(dst)*) - (*grid_dc.Clear(Avalonia.Media.Colors.Transparent)*) - (*grid_dc.PopClip()*) - - (*use r2 = grid_fb_scroll.PlatformImpl.CloneAs<_>()*) - (*grid_dc.DrawBitmap(r2, 1.0, dst, dst)*) - | Put r -> for row = r.row to r.row_end - 1 do draw row r.col r.col_end ) - let mutable drawn = vm.DrawOps.Count <> 0 - // for the base grid, do not block drawing of children, and - // mark child grid dirty if base updates overlapped it - if vm.GridId = grid_vm.GridId then - for cgrid in vm.ChildGrids do - let touched = _drawnRegions - |> Seq.exists(fun(r,c,ce) -> - cgrid.AnchorRow <= r && - r < cgrid.AnchorRow + cgrid.Rows && - not( cgrid.AnchorCol >= ce || c >= cgrid.AnchorCol + cgrid.Cols )) - if touched then cgrid.MarkAllDirty() - _drawnRegions.Clear() - for c in vm.ChildGrids do - let child_drawn = drawOps c - drawn <- drawn || child_drawn - drawn + vm.DrawOps.Count <> 0 + + let m_gridComparer = + let makeGridComparer() = + { new IComparer with + member __.Compare(x: GridViewModel, y: GridViewModel): int = + x.ZIndex - y.ZIndex + } + makeGridComparer() do this.Watch @@ -337,7 +318,7 @@ type Grid() as this = rpc.register.watch "font" (fun () -> if grid_vm <> Unchecked.defaultof<_> then - grid_vm.MarkAllDirty() + grid_vm.MarkDirty() this.InvalidateVisual()) // Input handling @@ -360,7 +341,13 @@ type Grid() as this = grid_dc.PushClip(Rect this.Bounds.Size) let timer = System.Diagnostics.Stopwatch.StartNew() _drawnRegions.Clear() - let drawn = drawOps grid_vm + _drawVMs.Clear() + scanDrawVMs grid_vm + _drawVMs.Sort(m_gridComparer) + let mutable drawn = false + for vm in _drawVMs do + let drawn' = drawOps vm + drawn <- drawn || drawn' if m_debug then drawDebug grid_dc grid_dc.PopClip() @@ -372,6 +359,7 @@ type Grid() as this = ctx.DrawImage(grid_fb, src_rect, tgt_rect, BitmapInterpolationMode.Default) timer.Stop() if drawn then trace grid_vm "drawing end, time = %dms." timer.ElapsedMilliseconds + else trace grid_vm "drawing end, nothing drawn." override this.MeasureOverride(size) = trace grid_vm "MeasureOverride: %A" size diff --git a/model.fs b/model.fs index 1d5074f..c16d76a 100644 --- a/model.fs +++ b/model.fs @@ -53,15 +53,34 @@ module private ModelImpl = let setTitle id title = frames.[id].Title <- title + let _unicast_fail id cmd = + trace "unicast into non-existing grid #%d: %A" id cmd + let unicast id cmd = match grids.TryGetValue id with | true, grid -> grid.Redraw cmd | _ -> trace "unicast into non-existing grid #%d: %A" id cmd + let unicast_detach id cmd = + match grids.TryGetValue id with + | true, grid -> + grid.Detach() + grid.Redraw cmd + | _ -> trace "unicast into non-existing grid #%d: %A" id cmd + + let unicast_change_parent id pid cmd = + match (grids.TryGetValue id),(grids.TryGetValue pid) with + | (true, grid),(true, parent) -> + grid.Detach() + parent.AddChild(grid) + grid.Redraw cmd + | _ -> trace "unicast into non-existing grid #%d or parent #%d: %A" id pid cmd + + let unicast_create id cmd w h = - if not(grids.ContainsKey id) then - add_grid <| grids.[1].CreateChild id h w - unicast id cmd + if not(grids.ContainsKey id) then + add_grid <| grids.[1].CreateChild id h w + unicast id cmd let broadcast cmd = for KeyValue(_,grid) in grids do @@ -97,10 +116,12 @@ module private ModelImpl = | GridCursorGoto _ -> broadcast cmd // Unicast - | GridClear id | GridScroll(id,_,_,_,_,_,_) - | WinClose id | WinFloatPos(id, _, _, _, _, _, _, _) | WinHide(id) - | WinExternalPos(id,_) + | GridClear id | GridScroll(id,_,_,_,_,_,_) + | WinClose id | WinHide(id) | WinViewport(id, _, _, _, _, _ ) -> unicast id cmd + | WinExternalPos(id,_) -> unicast_detach id cmd + | WinFloatPos(id, _, _, pid, _, _, _, _) + -> unicast_change_parent id pid cmd | MsgSetPos(id, _, _, _) -> unicast_create id cmd grids.[1].GridWidth 1 | WinPos(id, _, _, _, w, h) | GridResize(id, w, h) -> unicast_create id cmd w h diff --git a/ui.fs b/ui.fs index 6af985c..00c05d6 100644 --- a/ui.fs +++ b/ui.fs @@ -99,6 +99,7 @@ type IGridUI = abstract RenderScale: float abstract Redraw: RedrawCommand -> unit abstract CreateChild: id:int -> rows:int -> cols:int -> IGridUI + abstract AddChild: IGridUI -> unit abstract RemoveChild: IGridUI -> unit abstract Detach: unit -> unit