public static void Initialize(Dispatcher dispatcher)
        {
            Overlay.Initialize((background, foreground) =>
                               Control.Create(self =>
            {
                var canvas = (FrameworkElement)foreground.NativeHandle;

                Fusion.Application.MainThread.Schedule(() =>
                {
                    if (canvas.Parent != null)
                    {
                        ((Canvas)canvas.Parent).Children.Remove(canvas);
                    }
                });

                var foregroundWindow = new OverlayWindow
                {
                    Content = new Canvas
                    {
                        Children = { canvas }
                    },
                };

                var backgroundElement = background.NativeHandle as FrameworkElement;
                if (backgroundElement != null)
                {
                    background.Mount(self);
                }
                else
                {
                    var dummy = Shapes.Rectangle();
                    dummy.Mount(self);
                    backgroundElement = (FrameworkElement)dummy.NativeHandle;
                }

                Fusion.Application.MainThread.Schedule(() =>
                {
                    foregroundWindow.KeyDown += (sender, args) =>
                    {
                        var visual = PresentationSource.FromVisual(backgroundElement);
                        if (visual == null)
                        {
                            return;
                        }

                        if (args.Key == System.Windows.Input.Key.DeadCharProcessed)
                        {
                            return;
                        }

                        backgroundElement.RaiseEvent(new KeyEventArgs(System.Windows.Input.Keyboard.PrimaryDevice, visual, 0, args.Key)
                        {
                            RoutedEvent = System.Windows.Input.Keyboard.PreviewKeyDownEvent,
                            Source      = backgroundElement,
                        });
                    };

                    foregroundWindow.KeyUp += (sender, args) =>
                    {
                        var visual = PresentationSource.FromVisual(backgroundElement);
                        if (visual == null)
                        {
                            return;
                        }

                        if (args.Key == System.Windows.Input.Key.DeadCharProcessed)
                        {
                            return;
                        }

                        backgroundElement.RaiseEvent(new KeyEventArgs(System.Windows.Input.Keyboard.PrimaryDevice, visual, 0, args.Key)
                        {
                            RoutedEvent = System.Windows.Input.Keyboard.PreviewKeyUpEvent,
                            Source      = backgroundElement,
                        });
                    };
                });

                var windowFrames = backgroundElement.ScreenRect(dispatcher);
                var intersection = windowFrames.Select(f => f.Item2);
                var frame        = windowFrames.Select(f => f.Item1);
                var bounds       = frame.RelativeTo(intersection.Position()).Replay(1).RefCount();

                foreground.Mount(
                    new MountLocation.Mutable
                {
                    IsRooted      = self.IsRooted,
                    AvailableSize = self.AvailableSize,
                    NativeFrame   = bounds.Transpose(),
                });

                var parentWindow =
                    self.IsRooted.Switch(isRooted =>
                                         isRooted == false
                                                                ? Observable.Return(Optional.None <WindowWithOverlays>())
                                                                : backgroundElement.GetWindow <WindowWithOverlays>());

                parentWindow.SubscribeUsing(tmp =>
                                            tmp.MatchWith(
                                                none: () => Disposable.Empty,
                                                some: backgroundWindow =>
                {
                    var windowLocation = DataBinding.ObservableFromNativeEvent <object>(backgroundWindow, "LocationChanged")
                                         .StartWith(new object())
                                         .Select(_ => dispatcher.InvokeAsync(() => new Point <Points>(backgroundWindow.Left, backgroundWindow.Top)))
                                         .Switch();

                    dispatcher.Schedule(() =>
                    {
                        foregroundWindow.ShowActivated = false;
                        foregroundWindow.Show();
                        foregroundWindow.Owner = backgroundWindow;
                    });

                    return(Disposable.Combine(
                               backgroundWindow.AddOverlay("name", foregroundWindow),

                               Disposable.Create(() => dispatcher.Schedule(foregroundWindow.Hide)),

                               intersection.MoveTo(windowLocation)                                                 /*.Sample(Fusion.Application.PerFrame)*/
                               .Subscribe(s => dispatcher.Schedule(() =>
                    {
                        if (!double.IsInfinity(s.Left()))
                        {
                            foregroundWindow.Left = s.Left();
                        }
                        if (!double.IsInfinity(s.Top()))
                        {
                            foregroundWindow.Top = s.Top();
                        }
                        if (!double.IsInfinity(s.Width))
                        {
                            foregroundWindow.Width = s.Width.Max(0);
                        }
                        if (!double.IsInfinity(s.Height))
                        {
                            foregroundWindow.Height = s.Height.Max(0);
                        }
                    }))));
                }));

                return(backgroundElement);
            }));
        }
Example #2
0
        public static System.Windows.Window Initialize(Window model)
        {
            var dispatcher = Fusion.Application.MainThread;

            var windowSubject = new BehaviorSubject <Optional <WindowWithOverlays> >(Optional.None());

            var menu = new System.Windows.Controls.Menu();

            DockPanel.SetDock(menu, Dock.Top);

            var contentContainer = new Canvas()
            {
                // This is needed for native overlays to work
                ClipToBounds = true,
            };
            var panel = new DockPanel()
            {
                LastChildFill = true,
                Children      =
                {
                    menu,
                    contentContainer
                }
            };

            var ret = new FancyWindow(model.Style == WindowStyle.Regular || model.Style == WindowStyle.Fat)
            {
                Content = panel
            };

            var focused = new Subject <Unit>();

            model.Focused.Execute.Sample(focused).Subscribe(c => c());
            ret.Activated += (sender, args) => focused.OnNext(Unit.Default);

            var availableSize = DataBinding
                                .ObservableFromNativeEvent <SizeChangedEventArgs>(contentContainer, "SizeChanged")
                                .Select(e => e.NewSize.ToFusion())
                                .Replay(1);

            availableSize.Connect();

            var content = model.Content;
            //.SetNativeWindow(windowSubject)
            //.SetDensity(ret.DensityFactor);
            var transize = availableSize.Transpose();

            content.Mount(
                new MountLocation.Mutable
            {
                AvailableSize = transize,
                NativeFrame   = ObservableMath.RectangleWithSize(transize),
                IsRooted      = Observable
                                .FromEventPattern <DependencyPropertyChangedEventArgs>(ret, "IsVisibleChanged")
                                .Select(arg => ret.IsVisible)
                                .Replay(1).RefCount(),
            });

            content.BindNativeProperty(dispatcher, "Background", model.Background, color =>
                                       menu.Background = ret.Background = new SolidColorBrush(color.ToColor()));

            content.BindNativeProperty(dispatcher, "Foreground", model.Foreground, color =>
                                       menu.Foreground = ret.Foreground = new SolidColorBrush(color.ToColor()));

            content.BindNativeProperty(dispatcher, "BorderBrush", model.Border.Brush, color =>
                                       ret.BorderBrush = new SolidColorBrush(color.ToColor()));

            content.BindNativeProperty(dispatcher, "BorderThickness", model.Border.Thickness, thickness =>
                                       ret.BorderThickness = new System.Windows.Thickness(thickness));


            Fusion.Application.MainThread
            .InvokeAsync(() => content.NativeHandle)
            .ToObservable().OfType <UIElement>()
            .Subscribe(nativeContent =>
                       contentContainer.Children.Add(nativeContent));

            var windowState =
                model.State
                .Or(Property.Default <Optional <WindowState> >())
                .Or(WindowState.Normal)
                .AutoInvalidate()
                .PreventFeedback();

            windowState.Subscribe(state => dispatcher.InvokeAsync(() =>
                                                                  ret.WindowState = state == WindowState.Normal ? System.Windows.WindowState.Normal : System.Windows.WindowState.Maximized));

            ret.StateChanged += (sender, args) =>
                                windowState.Write(
                ret.WindowState == System.Windows.WindowState.Maximized
                                                ? WindowState.Maximized
                                                : WindowState.Normal);

            model.Size.Do(
                maybeSize =>
            {
                var size = maybeSize
                           .Or(Size.Create <Points>(Double.NaN, Double.NaN))
                           .AutoInvalidate(TimeSpan.FromSeconds(2))
                           .PreventFeedback();

                bool setByUs = false;

                size.Subscribe(s => dispatcher.Schedule(() =>
                {
                    setByUs       = true;
                    ret.Width     = (float)s.Width;
                    var newHeight = s.Height + (TitleBarHeight + menu.ActualHeight);
                    ret.Height    = (float)newHeight;
                    setByUs       = false;
                }));

                ret.SizeChanged += (s, a) =>
                {
                    if (setByUs)
                    {
                        return;
                    }

                    var contentSize = new Size <Points>(a.NewSize.Width, a.NewSize.Height - (TitleBarHeight + menu.ActualHeight));
                    size.Write(contentSize);
                };
            });

            model.Position.Do(
                maybePosition =>
            {
                var pos = maybePosition
                          .Or(new Point <Points>(double.NaN, double.NaN))
                          .AutoInvalidate(TimeSpan.FromSeconds(2))
                          .PreventFeedback();
                // TODO: ConnectWhile() ?

                bool setByUs = false;

                pos.Subscribe(p => dispatcher.Schedule(() =>
                {
                    setByUs  = true;
                    ret.Left = p.X;
                    ret.Top  = p.Y;
                    setByUs  = false;
                }));

                Fusion.Application.MainThread.Schedule(() =>
                {
                    ret.LocationChanged += (s, args) =>
                    {
                        if (setByUs)
                        {
                            return;
                        }

                        var sender = s as FancyWindow;
                        if (sender == null)
                        {
                            return;
                        }

                        pos.Write(new Point <Points>(sender.Left, sender.Top));
                    };
                });
            });

            model.TopMost.Do(topMost =>
                             topMost.Subscribe(t => dispatcher.Schedule(() =>
            {
                ret.Topmost = t;
                ret.Show();
            })));

            model.Size.Do(s =>
                          s.IsReadOnly.Subscribe(isReadOnly => dispatcher.Schedule(() =>
            {
                if (isReadOnly)
                {
                    ret.ResizeMode            = ResizeMode.NoResize;
                    ret.WindowStartupLocation = WindowStartupLocation.CenterScreen;
                }
            })));


            var closed = new Subject <Unit>();

            ret.Closed += (s, a) => closed.OnNext(Unit.Default);
            model.Closed.Execute.Sample(closed).Subscribe(e => e());

            model.Title.Subscribe(title => dispatcher.Schedule(() =>
            {
                ret.Title = model.HideTitle ? "" : title;
            }));

            model.Menu.Do(m => WindowsMenuBuilder.Populate(m, menu, ret, dispatcher));

            DataBinding
            .ObservableFromNativeEvent <EventArgs>(ret, "Loaded")
            .Subscribe(_ => windowSubject.OnNext(ret));

            DataBinding
            .ObservableFromNativeEvent <EventArgs>(ret, "LayoutUpdated")
            .Select(a => PresentationSource.FromVisual(ret))
            .DistinctUntilChanged()
            .Subscribe(
                presentation =>
            {
                if (presentation == null)
                {
                    return;
                }

                var winHandle = new WindowInteropHelper(ret).Handle;
                WindowPlacement.SetPlacement(winHandle, WindowPlacement.GetPlacement(winHandle));
            });

            ret.Closing += (s, a) =>
            {
                model.Closed.Execute.Take(1).Subscribe(e => e());
                ExitIfLastVisibleWindow(ret);
            };

            ret.IsVisibleChanged += (s, a) =>
            {
                if (ret.IsVisible == false)
                {
                    ExitIfLastVisibleWindow(ret);
                }
            };

            return(ret);
        }
        public static void Initialize(Dispatcher dispatcher)
        {
            Scrolling.Implementation.Factory = (content, darkTheme, supportsOpenGL, zooomAttribs, onBoundsChanged, scrollToRectangle, verticalScrollBarVisible, horizontalScrollBarVisible) =>
                                               Control.Create(self =>
            {
                var contentNativeFrame = ObservableMath.RectangleWithSize(content.DesiredSize.Max(self.NativeFrame.Size));

                content.Mount(
                    new MountLocation.Mutable
                {
                    IsRooted      = self.IsRooted,
                    NativeFrame   = contentNativeFrame,
                    AvailableSize = self.NativeFrame.Size,
                });

                var dummyControl = new Canvas();
                var view         = new ScrollViewer()
                {
                    ClipToBounds = true,
                    Content      = new ContentControl()
                    {
                        VerticalAlignment   = VerticalAlignment.Top,
                        HorizontalAlignment = HorizontalAlignment.Left,
                        Content             = dummyControl,
                    },

                    HorizontalScrollBarVisibility = horizontalScrollBarVisible ? ScrollBarVisibility.Auto : ScrollBarVisibility.Hidden,
                    VerticalScrollBarVisibility   = verticalScrollBarVisible ? ScrollBarVisibility.Auto : ScrollBarVisibility.Hidden,
                };

                self.BindNativeProperty(dispatcher, "containerWidth", contentNativeFrame.Size.Width, width => dummyControl.Width     = Math.Max(0.0, width));
                self.BindNativeProperty(dispatcher, "containerHeight", contentNativeFrame.Size.Height, height => dummyControl.Height = Math.Max(0.0, height));

                self.BindNativeProperty(dispatcher, "scrollTarget", scrollToRectangle,
                                        r =>
                {
                    dummyControl.BringIntoView(new Rect(r.Left(), r.Top(), r.Width, r.Height));
                });

                onBoundsChanged.Do(handler =>
                {
                    DataBinding.ObservableFromNativeEvent <EventArgs>(view, "ScrollChanged")
                    .CombineLatest(
                        contentNativeFrame.Transpose().DistinctUntilChanged(),
                        (_, contentFrame) => new ScrollBounds(
                            Rectangle.FromPositionSize <Points>(
                                view.HorizontalOffset,
                                view.VerticalOffset,
                                view.ViewportWidth,
                                view.ViewportHeight),
                            contentFrame))
                    .DistinctUntilChanged()
                    .Subscribe(handler);
                });

                self.BindNativeDefaults(view, dispatcher);

                var child = (FrameworkElement)content.NativeHandle;

                dummyControl.Children.Add(child);

                return(view);
            });
        }
        public IUnoHostControl Create(
            AbsoluteFilePath assemblyPath,
            Command onFocused,
            Menu contextMenu,
            AbsoluteFilePath userDataPath,
            Action <IUnoHostControl> initialize,
            Action <OpenGlVersion> gotVersion,
            params string[] arguments)
        {
            UnoHostProcess.Application = ExternalApplication.FromNativeExe(typeof(UnoHostControlFactory).Assembly.GetCodeBaseFilePath());

            var dispatcher = Fusion.Application.MainThread;

            var messagesToHost = new Subject <IBinaryMessage>();

            var unoHost = UnoHostProcess.Spawn(assemblyPath, messagesToHost, userDataPath, /*TODO*/ new List <string>());

            unoHost.Process.Subscribe(process => new Job().AddProcess(process.Id));

            var windowCreated = unoHost.Receieve(WindowCreatedMessage.TryParse).Replay(1);

            windowCreated.Connect();

            var control = new UnoHostControlImplementation()
            {
                _disposable = unoHost,
                Messages    = unoHost.Messages,
                MessagesTo  = messagesToHost,
                Process     = unoHost.Process,
            };

            control.Control =
                Fusion.Control.Create(location =>
            {
                var dummyControl = Shapes.Rectangle();
                dummyControl.Mount(location);
                var dummyElement = (FrameworkElement)dummyControl.NativeHandle;

                location.IsRooted
                .ObserveOn(dispatcher)
                .SubscribeUsing(rooted => rooted
                                                        ? ContextMenuImplementation.AddMenuTemporarily(dummyElement, contextMenu, dispatcher)
                                                        : Disposable.Empty);

                var mainWindow = DataBinding
                                 .ObservableFromNativeEvent <object>(dummyElement, "LayoutUpdated")
                                 .StartWith(new object())
                                 .Select(_ =>
                {
                    var hwndSource1 = PresentationSource.FromVisual(dummyElement);
                    if (hwndSource1 == null)
                    {
                        return(Optional.None <System.Windows.Window>());
                    }

                    var window = hwndSource1.RootVisual as System.Windows.Window;
                    if (window == null)
                    {
                        return(Optional.None <System.Windows.Window>());
                    }

                    return(Optional.Some(window));
                })
                                 .DistinctUntilChanged()
                                 .NotNone()
                                 .Replay(1).RefCount();

                var mainWindowHandle = mainWindow.Select(currentWindow =>
                {
                    var hwndSource = (HwndSource)PresentationSource.FromVisual(currentWindow);
                    if (hwndSource == null)
                    {
                        return(Optional.None());
                    }

                    return(Optional.Some(hwndSource.Handle));
                })
                                       .NotNone()
                                       .Replay(1);

                mainWindowHandle.Connect();

                var focused = unoHost.Receieve(WindowFocusMessage.TryParse)
                              .Where(focusState => focusState == FocusState.Focused);

                focused.CombineLatest(mainWindowHandle, (a, b) => b)
                .ObserveOn(dispatcher)
                .Subscribe(t => WinApi.ShowWindow(t, WinApi.ShowWindowEnum.ShowNoActivate));

                unoHost.Receieve(WindowContextMenuMessage.TryParse)
                .ObserveOn(dispatcher)
                .Subscribe(t => dummyElement.ContextMenu.IsOpen = t);

                unoHost.Receieve(WindowMouseScrollMessage.TryParse)
                .ObserveOn(dispatcher)
                .Subscribe(deltaWheel =>
                {
                    var delta       = deltaWheel;
                    var routedEvent = Mouse.MouseWheelEvent;

                    dummyElement.RaiseEvent(
                        new MouseWheelEventArgs(
                            Mouse.PrimaryDevice,
                            Environment.TickCount, delta)
                    {
                        RoutedEvent = routedEvent,
                        Source      = dummyElement
                    });
                });


                // This is a fix for a commit that "fixed" handling the shortcuts, but broke just
                // plain typing in a TextInput.
                // See https://github.com/fusetools/Fuse/issues/3342
                // and https://github.com/fusetools/Fuse/issues/3887

                // Workaround to fix a problem with handling shortcut keys in the application while
                // the viewport has focus and the user is typing in an TextInput in the app.
                // We have give the main window focus to handle the shortcut keys, but then
                // the viewport will lose focus. Current work around is to only do this when
                // the user presses down Ctrl.

                unoHost.Receieve(WindowKeyDown.TryParse)
                .WithLatestFromBuffered(mainWindow, (t, w) => new { Keys = t, Window = w })
                .ObserveOn(dispatcher)
                .Subscribe(t =>
                {
                    var modifiers = System.Windows.Input.Keyboard.PrimaryDevice.Modifiers;
                    var alt       = modifiers.HasFlag(System.Windows.Input.ModifierKeys.Alt);

                    // Activate the main window if the Ctrl-key was pressed.
                    // Ctrl + Alt means AltGr which we want to keep in the Uno host e.g. for the '@' key on Nordic keyboards.
                    if (t.Keys == Keys.ControlKey && !alt)
                    {
                        t.Window.Activate();
                    }
                });

                focused
                .WithLatestFromBuffered(onFocused.Execute.ConnectWhile(dummyControl.IsRooted), (_, c) => c)
                .Subscribe(c => c());

                var overlayForm = new OverlayForm();

                Observable
                .CombineLatest(windowCreated, mainWindowHandle,
                               (viewportHwnd, mainHwnd) => new
                {
                    ViewportHwnd = viewportHwnd,
                    MainHwnd     = mainHwnd,
                })
                .ObserveOn(dispatcher)
                .SubscribeUsing(t =>
                                overlayForm.BindTo(t.ViewportHwnd, t.MainHwnd, dummyControl, dummyElement.GetDpi()));

                unoHost.Messages
                .SelectMany(m => m.TryParse(Ready.MessageType, Ready.ReadDataFrom))
                .Take(1)
                .Subscribe(_ => initialize(control));

                unoHost.Messages
                .SelectSome(OpenGlVersionMessage.TryParse)
                .Subscribe(gotVersion);

                unoHost.Messages.Connect();


                return(dummyElement);
            });

            return(control);
        }
        static void BindText(
            this System.Windows.Controls.TextBox control,
            IProperty <string> value,
            IObservable <bool> isRooted,
            bool isMultiline)
        {
            bool valueSetByUser    = true;
            bool hasUnsavedChanges = false;

            value = value
                    .ConnectWhile(isRooted)
                    .DistinctUntilChangedOrSet();

            DataBinding
            .ObservableFromNativeEvent <EventArgs>(control, "TextChanged")
            .Subscribe(_ =>
            {
                if (!valueSetByUser)
                {
                    return;
                }

                hasUnsavedChanges = true;
                value.Write(control.Text, save: false);
            });

            value.Subscribe(v =>
                            Fusion.Application.MainThread.Schedule(() =>
            {
                valueSetByUser = false;
                try
                {
                    control.Text = v;
                }
                finally
                {
                    valueSetByUser = true;
                }
            }));

            control.LostKeyboardFocus += (s, a) =>
            {
                if (!hasUnsavedChanges)
                {
                    return;
                }

                value.Write(control.Text, save: true);
                hasUnsavedChanges = false;
            };

            if (isMultiline == false)
            {
                control.KeyDown += (s, a) =>
                {
                    if (a.Key != System.Windows.Input.Key.Return)
                    {
                        return;
                    }

                    if (!hasUnsavedChanges)
                    {
                        return;
                    }

                    value.Write(control.Text, save: true);
                    hasUnsavedChanges = false;
                };
            }
        }