// Main thread public IDisposable BindTo(IntPtr unoHostHwnd, IntPtr controlHwnd, IControl control, IObservable <Ratio <Pixels, Points> > density) { var element = control.NativeHandle as FrameworkElement; if (element == null) { throw new InvalidOperationException("Can't bind to an empty control"); } var frames = DataBinding .ObservableFromNativeEvent <object>(element, "LayoutUpdated") .StartWith(new object()) .Select(_ => { var r = element.GetScreenRect(); return(new ClipResult { BoundsInIntersection = r.Item1.RelativeTo(r.Item2.Position), IntersectionInParent = r.Item2 }); }); WinApi.SetWindowParent(unoHostHwnd, Handle); WinApi.SetWindowParent(Handle, controlHwnd); WinApi.ShowWindow(unoHostHwnd, WinApi.ShowWindowEnum.ShowNoActivate); var flags = WinApi.SWP_NOZORDER | WinApi.SWP_NOACTIVATE; return(Disposable.Combine ( Disposable.Create(() => Invoke(new Action(Hide))), frames.BufferPrevious().WithLatestFromBuffered(density, Tuple.Create).Subscribe( (f, d) => Fusion.Application.MainThread.Schedule(() => { WinApi.SetWindowPos( Handle, f.Current.IntersectionInParent.Position * d, // Bug in Window chrome, where position must be set once more, even though it has been set. f.ChangesTo(fr => fr.IntersectionInParent.Size * d), f.Current.IntersectionInParent.Size.HasZeroArea(), flags); WinApi.SetWindowPos( unoHostHwnd, f.ChangesTo(fr => fr.BoundsInIntersection.Position * d), f.ChangesTo(fr => fr.BoundsInIntersection.Size * d), f.Current.BoundsInIntersection.Size.HasZeroArea(), flags | WinApi.SWP_ASYNCWINDOWPOS); })) )); }
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); }