From e57b4703679521eb26279300547a30c544ac16ee Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 22 Jan 2025 10:49:12 -0800 Subject: [PATCH 01/14] Convert `NoBorderBtn` to Flow. --- .../common/swt/widgets/NoBorderBtn.java | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/durian-swt/src/main/java/com/diffplug/common/swt/widgets/NoBorderBtn.java b/durian-swt/src/main/java/com/diffplug/common/swt/widgets/NoBorderBtn.java index 7c3e8c3a..6dcc0865 100644 --- a/durian-swt/src/main/java/com/diffplug/common/swt/widgets/NoBorderBtn.java +++ b/durian-swt/src/main/java/com/diffplug/common/swt/widgets/NoBorderBtn.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 DiffPlug + * Copyright (C) 2020-2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,10 +15,10 @@ */ package com.diffplug.common.swt.widgets; - +import com.diffplug.common.rx.Rx; import com.diffplug.common.swt.ControlWrapper; -import io.reactivex.Observable; -import io.reactivex.subjects.PublishSubject; +import kotlinx.coroutines.flow.Flow; +import kotlinx.coroutines.flow.MutableSharedFlow; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; @@ -38,7 +38,7 @@ public class NoBorderBtn extends ControlWrapper.AroundControl { private Image img = null; private Rectangle imgBounds; private boolean enabled = true; - private PublishSubject selection = PublishSubject.create(); + private MutableSharedFlow selection = Rx.INSTANCE.createEmitFlow(); public NoBorderBtn(Composite parent) { super(new Canvas(parent, SWT.NONE)); @@ -61,17 +61,13 @@ public NoBorderBtn(Composite parent) { // send a selection event on each click (if we aren't disabled) wrapped.addListener(SWT.MouseDown, e -> { if (enabled) { - selection.onNext(this); + Rx.emit(selection, this); } }); - // send a "completed" event when we finish - wrapped.addListener(SWT.Dispose, e -> { - selection.onComplete(); - }); } /** Returns an Observable which responds to clicks. */ - public Observable clicked() { + public Flow clicked() { return selection; } From 36c51b65f71d320f8119df97b3ed9faeab8f42c6 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 22 Jan 2025 10:50:53 -0800 Subject: [PATCH 02/14] Add `.gitignore` --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 12308e51..61ac0877 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ # gradle stuff .gradle/ build/ +.kotlin/ # IntelliJ .idea From 0d04c0c449d854ab816a10b6bb8f622469ca8e68 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 22 Jan 2025 11:54:05 -0800 Subject: [PATCH 03/14] Remove RxJava `Scheduler`. --- .../java/com/diffplug/common/swt/SwtExec.java | 268 +----------------- .../diffplug/common/swt/SwtExecProfile.java | 16 +- 2 files changed, 11 insertions(+), 273 deletions(-) diff --git a/durian-swt/src/main/java/com/diffplug/common/swt/SwtExec.java b/durian-swt/src/main/java/com/diffplug/common/swt/SwtExec.java index 2ebabb36..91e9a787 100644 --- a/durian-swt/src/main/java/com/diffplug/common/swt/SwtExec.java +++ b/durian-swt/src/main/java/com/diffplug/common/swt/SwtExec.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2022 DiffPlug + * Copyright (C) 2020-2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,30 +27,19 @@ import com.diffplug.common.util.concurrent.MoreExecutors; import com.diffplug.common.util.concurrent.Runnables; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.reactivex.Scheduler; -import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.disposables.Disposable; -import io.reactivex.disposables.Disposables; -import io.reactivex.schedulers.Schedulers; -import java.util.HashSet; import java.util.List; import java.util.Objects; -import java.util.Set; import java.util.concurrent.AbstractExecutorService; import java.util.concurrent.Callable; import java.util.concurrent.Delayed; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; -import java.util.concurrent.Future; -import java.util.concurrent.FutureTask; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RunnableFuture; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; -import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.function.Function; import java.util.function.Supplier; import kotlin.coroutines.CoroutineContext; @@ -332,7 +321,7 @@ public RxExecutor getRxExecutor() { } SwtExec() { - this(exec -> Rx.callbackOn(exec, new SwtScheduler(exec), new SwtDispatcher(exec))); + this(exec -> Rx.callbackOn(exec, new SwtDispatcher(exec))); } SwtExec(Function rxExecutorCreator) { @@ -722,206 +711,6 @@ public MainCoroutineDispatcher getImmediate() { } } - /** Scheduler that runs tasks on Swt's event dispatch thread. */ - static final class SwtScheduler extends Scheduler { - final SwtExec exec; - - public SwtScheduler(SwtExec exec) { - this.exec = exec; - } - - @Override - public Worker createWorker() { - return new SwtWorker(exec); - } - - static final class SwtWorker extends Scheduler.Worker { - final SwtExec exec; - - volatile boolean unsubscribed; - - /** Set of active tasks, guarded by this. */ - Set tasks; - - public SwtWorker(SwtExec exec) { - this.exec = exec; - this.tasks = new HashSet<>(); - } - - @Override - public void dispose() { - if (unsubscribed) { - return; - } - unsubscribed = true; - - Set set; - synchronized (this) { - set = tasks; - tasks = null; - } - - if (set != null) { - for (SwtScheduledAction a : set) { - a.cancelFuture(); - } - } - } - - void remove(SwtScheduledAction a) { - if (unsubscribed) { - return; - } - synchronized (this) { - if (unsubscribed) { - return; - } - - tasks.remove(a); - } - } - - @Override - public boolean isDisposed() { - return unsubscribed; - } - - @Override - public Disposable schedule(Runnable action) { - if (unsubscribed) { - return Disposables.disposed(); - } - - SwtScheduledAction a = new SwtScheduledAction(action, this); - - synchronized (this) { - if (unsubscribed) { - return Disposables.disposed(); - } - - tasks.add(a); - } - - exec.execute(a); - - if (unsubscribed) { - a.cancel(); - return Disposables.disposed(); - } - - return a; - } - - @Override - public Disposable schedule(Runnable action, long delayTime, TimeUnit unit) { - if (unsubscribed) { - return Disposables.disposed(); - } - - SwtScheduledAction a = new SwtScheduledAction(action, this); - - synchronized (this) { - if (unsubscribed) { - return Disposables.disposed(); - } - - tasks.add(a); - } - - Future f = exec.schedule(a, delayTime, unit); - - if (unsubscribed) { - a.cancel(); - f.cancel(true); - return Disposables.disposed(); - } - - a.setFuture(f); - - return a; - } - - /** - * Represents a cancellable asynchronous Runnable that wraps an action - * and manages the associated Worker lifecycle. - */ - static final class SwtScheduledAction implements Runnable, Disposable { - final Runnable action; - - final SwtWorker parent; - - volatile Future future; - @SuppressWarnings("rawtypes") - static final AtomicReferenceFieldUpdater FUTURE = AtomicReferenceFieldUpdater.newUpdater(SwtScheduledAction.class, Future.class, "future"); - - static final Future CANCELLED = new FutureTask<>(() -> {}, null); - - static final Future FINISHED = new FutureTask<>(() -> {}, null); - - volatile int state; - static final AtomicIntegerFieldUpdater STATE = AtomicIntegerFieldUpdater.newUpdater(SwtScheduledAction.class, "state"); - - static final int STATE_ACTIVE = 0; - static final int STATE_FINISHED = 1; - static final int STATE_CANCELLED = 2; - - public SwtScheduledAction(Runnable action, SwtWorker parent) { - this.action = action; - this.parent = parent; - } - - @Override - public void run() { - if (!parent.unsubscribed && state == STATE_ACTIVE) { - try { - action.run(); - } finally { - FUTURE.lazySet(this, FINISHED); - if (STATE.compareAndSet(this, STATE_ACTIVE, STATE_FINISHED)) { - parent.remove(this); - } - } - } - } - - @Override - public boolean isDisposed() { - return state != STATE_ACTIVE; - } - - @Override - public void dispose() { - if (STATE.compareAndSet(this, STATE_ACTIVE, STATE_CANCELLED)) { - parent.remove(this); - } - cancelFuture(); - } - - void setFuture(Future f) { - if (FUTURE.compareAndSet(this, null, f)) { - if (future != FINISHED) { - f.cancel(true); - } - } - } - - void cancelFuture() { - Future f = future; - if (f != CANCELLED && f != FINISHED) { - f = FUTURE.getAndSet(this, CANCELLED); - if (f != null && f != CANCELLED && f != FINISHED) { - f.cancel(true); - } - } - } - - void cancel() { - state = STATE_CANCELLED; - } - } - } - } - /** Global executor for actions which should only execute immediately on the SWT thread. */ private static SwtExec swtOnly; @@ -937,7 +726,7 @@ void cancel() { @SuppressFBWarnings(value = "LI_LAZY_INIT_STATIC", justification = "This race condition is fine, see comment in SwtExec.blocking()") public static SwtExec swtOnly() { if (swtOnly == null) { - swtOnly = new SwtExec(exec -> Rx.callbackOn(exec, new SwtOnlyScheduler(), new SwtOnlyDispatcher())) { + swtOnly = new SwtExec(exec -> Rx.callbackOn(exec, new SwtOnlyDispatcher())) { @Override public void execute(Runnable runnable) { requireNonNull(runnable); @@ -968,55 +757,6 @@ public void dispatch(@NotNull CoroutineContext coroutineContext, @NotNull Runnab } } - /** - * Copied straight from rx.schedulers.ImmediateScheduler, - * but checks for the SWT thread before running stuff, - * and handles future-scheduling correctly. - */ - static final class SwtOnlyScheduler extends Scheduler { - @Override - public Worker createWorker() { - return new InnerImmediateScheduler(); - } - - private static final class InnerImmediateScheduler extends Scheduler.Worker { - final Disposable innerSubscription = Disposables.empty(); - - @Override - public Disposable schedule(Runnable action, long delayTime, TimeUnit unit) { - CompositeDisposable sub = new CompositeDisposable(); - Future future = SwtExec.async().schedule(() -> { - if (!sub.isDisposed()) { - action.run(); - sub.dispose(); - } - }, delayTime, unit); - sub.add(Disposables.fromFuture(future)); - return sub; - } - - @Override - public Disposable schedule(Runnable action) { - if (Thread.currentThread() == swtThread) { - action.run(); - } else { - SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS); - } - return Disposables.disposed(); - } - - @Override - public void dispose() { - innerSubscription.dispose(); - } - - @Override - public boolean isDisposed() { - return innerSubscription.isDisposed(); - } - } - } - private static class SameThreadCoroutineDispatcher extends CoroutineDispatcher { @Override public void dispatch(@NotNull CoroutineContext coroutineContext, @NotNull Runnable runnable) { @@ -1024,7 +764,7 @@ public void dispatch(@NotNull CoroutineContext coroutineContext, @NotNull Runnab } } - private static final SwtExec sameThread = new SwtExec(exec -> Rx.callbackOn(MoreExecutors.directExecutor(), Schedulers.trampoline(), new SameThreadCoroutineDispatcher())) { + private static final SwtExec sameThread = new SwtExec(exec -> Rx.callbackOn(MoreExecutors.directExecutor(), new SameThreadCoroutineDispatcher())) { @Override public void execute(Runnable runnable) { requireNonNull(runnable); diff --git a/durian-swt/src/test/java/com/diffplug/common/swt/SwtExecProfile.java b/durian-swt/src/test/java/com/diffplug/common/swt/SwtExecProfile.java index 3fedadd3..11ace53e 100644 --- a/durian-swt/src/test/java/com/diffplug/common/swt/SwtExecProfile.java +++ b/durian-swt/src/test/java/com/diffplug/common/swt/SwtExecProfile.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 DiffPlug + * Copyright (C) 2020-2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,11 +21,11 @@ import com.diffplug.common.debug.LapTimer; import com.diffplug.common.rx.Rx; import io.reactivex.disposables.Disposable; -import io.reactivex.subjects.PublishSubject; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.RejectedExecutionException; import java.util.function.Consumer; +import kotlinx.coroutines.flow.MutableSharedFlow; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Widget; import org.junit.Test; @@ -131,18 +131,17 @@ public void addSwtExec(String name, SwtExec underTest) { public void run(Widget guard) { JuxtaProfiler profiler = new JuxtaProfiler(); profiler.addTestNanoWrap2Sec("control", () -> { - PublishSubject subject = PublishSubject.create(); - subject.subscribe(val -> {}); + MutableSharedFlow subject = Rx.INSTANCE.createEmitFlow(); drain(subject); }); toProfile.forEach((name, underTest) -> { profiler.addTest(name, new JuxtaProfiler.InitTimedCleanup(LapTimer.createNanoWrap2Sec()) { - PublishSubject subject; + MutableSharedFlow subject; Disposable sub; @Override protected void init() throws Throwable { - subject = PublishSubject.create(); + subject = Rx.INSTANCE.createEmitFlow(); sub = underTest.guardOn(guard).subscribeDisposable(subject, val -> {}); } @@ -162,11 +161,10 @@ protected void cleanup() throws Throwable { profiler.runRandomTrials(NUM_TRIALS); } - private static void drain(PublishSubject subject) { + private static void drain(MutableSharedFlow subject) { for (int i = 0; i < EVENTS_TO_PUSH; ++i) { - subject.onNext(0); + Rx.emit(subject, 0); } - subject.onComplete(); } } From 92b5ee9e404672b128c30fba5545b5130259cc70 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 23 Jan 2025 00:12:01 -0800 Subject: [PATCH 04/14] Disposable -> Job --- .../main/java/com/diffplug/common/swt/Shells.kt | 15 ++++++++------- .../com/diffplug/common/swt/SwtExecProfile.java | 6 +++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/durian-swt/src/main/java/com/diffplug/common/swt/Shells.kt b/durian-swt/src/main/java/com/diffplug/common/swt/Shells.kt index 79b1820d..0a4f8695 100644 --- a/durian-swt/src/main/java/com/diffplug/common/swt/Shells.kt +++ b/durian-swt/src/main/java/com/diffplug/common/swt/Shells.kt @@ -21,8 +21,7 @@ import com.diffplug.common.swt.os.WS import com.diffplug.common.tree.TreeIterable import com.diffplug.common.tree.TreeQuery import com.diffplug.common.tree.TreeStream -import io.reactivex.disposables.Disposable -import io.reactivex.disposables.Disposables +import kotlinx.coroutines.Job import org.eclipse.swt.SWT import org.eclipse.swt.graphics.Image import org.eclipse.swt.graphics.Point @@ -36,7 +35,7 @@ import java.util.* /** A fluent builder for creating SWT [Shell]s. */ class Shells private constructor(private val style: Int, private val coat: Coat) { - private var title: String = "" + private var title: String? = null private var image: Image? = null private var alpha = SWT.DEFAULT private val size = Point(SWT.DEFAULT, SWT.DEFAULT) @@ -434,7 +433,7 @@ class Shells private constructor(private val style: Int, private val coat: Coat) /** Prevents the given shell from closing without prompting. Returns a Subscription which can cancel this blocking. */ @JvmStatic - fun confirmClose(shell: Shell, title: String, question: String, runOnClose: Runnable): Disposable { + fun confirmClose(shell: Shell, title: String, question: String, runOnClose: Runnable): Job { val listener = Listener { e: Event -> e.doit = SwtMisc.blockForQuestion(title, question, shell) if (e.doit) { @@ -442,9 +441,11 @@ class Shells private constructor(private val style: Int, private val coat: Coat) } } shell.addListener(SWT.Close, listener) - return Disposables.fromRunnable { - SwtExec.immediate().guardOn(shell).execute { - shell.removeListener(SWT.Close, listener) + return Job().apply { + invokeOnCompletion { + SwtExec.immediate().guardOn(shell).execute { + shell.removeListener(SWT.Close, listener) + } } } } diff --git a/durian-swt/src/test/java/com/diffplug/common/swt/SwtExecProfile.java b/durian-swt/src/test/java/com/diffplug/common/swt/SwtExecProfile.java index 11ace53e..56bd4e63 100644 --- a/durian-swt/src/test/java/com/diffplug/common/swt/SwtExecProfile.java +++ b/durian-swt/src/test/java/com/diffplug/common/swt/SwtExecProfile.java @@ -20,11 +20,11 @@ import com.diffplug.common.debug.JuxtaProfiler; import com.diffplug.common.debug.LapTimer; import com.diffplug.common.rx.Rx; -import io.reactivex.disposables.Disposable; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.RejectedExecutionException; import java.util.function.Consumer; +import kotlinx.coroutines.Job; import kotlinx.coroutines.flow.MutableSharedFlow; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Widget; @@ -137,7 +137,7 @@ public void run(Widget guard) { toProfile.forEach((name, underTest) -> { profiler.addTest(name, new JuxtaProfiler.InitTimedCleanup(LapTimer.createNanoWrap2Sec()) { MutableSharedFlow subject; - Disposable sub; + Job sub; @Override protected void init() throws Throwable { @@ -152,7 +152,7 @@ protected void timed() throws Throwable { @Override protected void cleanup() throws Throwable { - sub.dispose(); + sub.cancel(null); subject = null; sub = null; } From 2188a9f6cca24aa0eb460874fd577ed9ad5524e1 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 23 Jan 2025 08:46:00 -0800 Subject: [PATCH 05/14] Delete unnecessary CI jobs. --- .github/workflows/changelog-print.yml | 22 ------------------ .../workflows/gradle-wrapper-validation.yml | 23 ------------------- 2 files changed, 45 deletions(-) delete mode 100644 .github/workflows/changelog-print.yml delete mode 100644 .github/workflows/gradle-wrapper-validation.yml diff --git a/.github/workflows/changelog-print.yml b/.github/workflows/changelog-print.yml deleted file mode 100644 index a3009e3a..00000000 --- a/.github/workflows/changelog-print.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: changelogPrint - -on: - push: - branches: [main] - -jobs: - build: - runs-on: ubuntu-latest - name: changelogPrint - steps: - - uses: actions/checkout@v3 - - name: jdk 11 - uses: actions/setup-java@v3 - with: - java-version: 11 - distribution: 'temurin' - - name: gradle caching - uses: gradle/gradle-build-action@v2 - with: - gradle-home-cache-cleanup: true - - run: ./gradlew changelogPrint diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml deleted file mode 100644 index 2c02ce0f..00000000 --- a/.github/workflows/gradle-wrapper-validation.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: "Validate Gradle Wrapper" -on: - push: - paths: - - 'gradlew' - - 'gradlew.bat' - - 'gradle/wrapper/' - pull_request: - paths: - - 'gradlew' - - 'gradlew.bat' - - 'gradle/wrapper/' - -permissions: - contents: read - -jobs: - validation: - name: "Validation" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: gradle/wrapper-validation-action@v1 From 048cbd9d1941801894e66cbbead1af737afe400d Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 23 Jan 2025 08:48:18 -0800 Subject: [PATCH 06/14] Try to run durian-swt CI against durian-rx using included build. --- .github/workflows/deploy.yml | 1 + settings.gradle | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f0996492..8cf6564d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -31,6 +31,7 @@ jobs: ORG_GRADLE_PROJECT_gpg_key64: ${{ secrets.GPG_KEY64 }} steps: - uses: actions/checkout@v4 + - run: git clone https://github.com/diffplug/durian-rx .../durian-rx - name: jdk 17 uses: actions/setup-java@v4 with: diff --git a/settings.gradle b/settings.gradle index 0eb06ca7..51e237f9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -39,6 +39,8 @@ blowdryerSetup { rootProject.name = 'durian-swt-root' +includeBuild '../durian-rx' + include 'durian-swt' include 'durian-swt.os' include 'durian-swt.cocoa.macosx.aarch64' From 803d114d4e354fb69d9d998eed3f3adc0ce18a0e Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 23 Jan 2025 08:59:36 -0800 Subject: [PATCH 07/14] Oops, tweaked deploy rather than ci. --- .github/workflows/ci.yml | 1 + .github/workflows/deploy.yml | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d56c465..cf295c6d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,7 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + - run: git clone https://github.com/diffplug/durian-rx .../durian-rx - name: Install JDK ${{ matrix.jre }} uses: actions/setup-java@v4 with: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8cf6564d..f0996492 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -31,7 +31,6 @@ jobs: ORG_GRADLE_PROJECT_gpg_key64: ${{ secrets.GPG_KEY64 }} steps: - uses: actions/checkout@v4 - - run: git clone https://github.com/diffplug/durian-rx .../durian-rx - name: jdk 17 uses: actions/setup-java@v4 with: From 193c31dd68e6d7fd5db71c50407c4a26c2c1a666 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 23 Jan 2025 09:13:48 -0800 Subject: [PATCH 08/14] Oops, `...` vs `..` --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf295c6d..1da20218 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - run: git clone https://github.com/diffplug/durian-rx .../durian-rx + - run: git clone https://github.com/diffplug/durian-rx ../durian-rx - name: Install JDK ${{ matrix.jre }} uses: actions/setup-java@v4 with: From 952b0f2cba12e09415eea7f6cee2c4a9a8a78951 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 23 Jan 2025 09:19:58 -0800 Subject: [PATCH 09/14] Remove rxjava dep completely. --- build.gradle | 1 - durian-swt/build.gradle | 1 - gradle.properties | 1 - 3 files changed, 3 deletions(-) diff --git a/build.gradle b/build.gradle index e1d8ede8..84ffc6c0 100644 --- a/build.gradle +++ b/build.gradle @@ -25,7 +25,6 @@ subprojects { subProject -> "https://javadoc.io/doc/com.diffplug.durian/durian-concurrent/${VER_DURIAN}", "https://javadoc.io/doc/com.diffplug.durian/durian-debug/${VER_DURIAN_DEBUG}", "https://javadoc.io/doc/com.diffplug.durian/durian-rx/${VER_DURIAN_RX}", - "https://javadoc.io/doc/io.reactivex.rxjava2/rxjava/${VER_RXJAVA}", 'https://docs.oracle.com/javase/8/docs/api/' ].join(' ') diff --git a/durian-swt/build.gradle b/durian-swt/build.gradle index 47a3a822..eaba880e 100644 --- a/durian-swt/build.gradle +++ b/durian-swt/build.gradle @@ -22,7 +22,6 @@ p2deps { dependencies { api project(':durian-swt.os') api "com.diffplug.durian:durian-rx:$VER_DURIAN_RX" - api "io.reactivex.rxjava2:rxjava:$VER_RXJAVA" implementation "com.diffplug.durian:durian-core:$VER_DURIAN" implementation "com.diffplug.durian:durian-collect:$VER_DURIAN" implementation "com.diffplug.durian:durian-concurrent:$VER_DURIAN" diff --git a/gradle.properties b/gradle.properties index 58e53c64..ea7cb567 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,7 +15,6 @@ VER_FINDBUGS=3.0.1 VER_DURIAN=1.2.0 VER_DURIAN_RX=4.0.0 VER_DURIAN_DEBUG=1.0.0 -VER_RXJAVA=2.0.0 # SWT Dependencies from P2 SWT_VERSION=4.21 SWT_VERSION_X86=4.7 From 80ae3da1f6f41af16600109309e28e8d24e0914f Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 23 Jan 2025 13:01:25 -0800 Subject: [PATCH 10/14] Rename `asObservable` to `asFlow` --- .../src/main/java/com/diffplug/common/swt/SwtThread.java | 6 +++--- .../main/java/com/diffplug/common/swt/jface/ViewerMisc.java | 5 ++--- .../java/com/diffplug/common/swt/widgets/RadioGroup.java | 5 ++--- .../com/diffplug/common/swt/widgets/RadioGroupTest.java | 5 ++--- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/durian-swt/src/main/java/com/diffplug/common/swt/SwtThread.java b/durian-swt/src/main/java/com/diffplug/common/swt/SwtThread.java index 0dc37930..80c2a23c 100644 --- a/durian-swt/src/main/java/com/diffplug/common/swt/SwtThread.java +++ b/durian-swt/src/main/java/com/diffplug/common/swt/SwtThread.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 DiffPlug + * Copyright (C) 2020-2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ */ package com.diffplug.common.swt; - +import com.diffplug.common.rx.IFlowable; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -35,7 +35,7 @@ * * When annotated on a parameter or field, it * must be either an {@link rx.Observable}, - * {@link com.diffplug.common.rx.IObservable}, or a + * {@link IFlowable}, or a * {@link java.util.concurrent.CompletionStage}, and * it indicates that the given code should only be * set and listened to from SWT. diff --git a/durian-swt/src/main/java/com/diffplug/common/swt/jface/ViewerMisc.java b/durian-swt/src/main/java/com/diffplug/common/swt/jface/ViewerMisc.java index 6d831053..4076c7c0 100644 --- a/durian-swt/src/main/java/com/diffplug/common/swt/jface/ViewerMisc.java +++ b/durian-swt/src/main/java/com/diffplug/common/swt/jface/ViewerMisc.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 DiffPlug + * Copyright (C) 2020-2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,6 @@ */ package com.diffplug.common.swt.jface; - import com.diffplug.common.base.Converter; import com.diffplug.common.base.Preconditions; import com.diffplug.common.collect.ImmutableList; @@ -55,7 +54,7 @@ public static void singleSelection(StructuredViewer viewer, RxBox { + SwtExec.immediate().guardOn(viewer.getControl()).subscribe(box, optional -> { if (optional.isPresent()) { viewer.setSelection(new StructuredSelection(optional.get())); } else { diff --git a/durian-swt/src/main/java/com/diffplug/common/swt/widgets/RadioGroup.java b/durian-swt/src/main/java/com/diffplug/common/swt/widgets/RadioGroup.java index b614c21f..592dd244 100644 --- a/durian-swt/src/main/java/com/diffplug/common/swt/widgets/RadioGroup.java +++ b/durian-swt/src/main/java/com/diffplug/common/swt/widgets/RadioGroup.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 DiffPlug + * Copyright (C) 2020-2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,6 @@ */ package com.diffplug.common.swt.widgets; - import com.diffplug.common.base.Preconditions; import com.diffplug.common.collect.BiMap; import com.diffplug.common.collect.HashBiMap; @@ -84,7 +83,7 @@ public RxBox buildOn(Composite cmp) { // when the selection is changed, set the button Button firstBtn = Iterables.get(mapping.keySet(), 0); - SwtExec.immediate().guardOn(firstBtn).subscribe(selection.asObservable(), obj -> { + SwtExec.immediate().guardOn(firstBtn).subscribe(selection, obj -> { Button selectedBtn = mapping.inverse().get(obj); for (Button btn : mapping.keySet()) { btn.setSelection(btn == selectedBtn); diff --git a/durian-swt/src/test/java/com/diffplug/common/swt/widgets/RadioGroupTest.java b/durian-swt/src/test/java/com/diffplug/common/swt/widgets/RadioGroupTest.java index 4911e9e1..cb62148d 100644 --- a/durian-swt/src/test/java/com/diffplug/common/swt/widgets/RadioGroupTest.java +++ b/durian-swt/src/test/java/com/diffplug/common/swt/widgets/RadioGroupTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 DiffPlug + * Copyright (C) 2020-2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,6 @@ */ package com.diffplug.common.swt.widgets; - import com.diffplug.common.rx.RxBox; import com.diffplug.common.swt.InteractiveTest; import com.diffplug.common.swt.Layouts; @@ -65,7 +64,7 @@ public void test() { readLbl.setText("Read selection: "); Text readTxt = new Text(debugParent, SWT.BORDER | SWT.SINGLE | SWT.READ_ONLY); - SwtExec.immediate().guardOn(readTxt).subscribe(selection.asObservable(), option -> { + SwtExec.immediate().guardOn(readTxt).subscribe(selection, option -> { readTxt.setText(option.name()); }); From 5caaf6e30b6d15ed6b9b6fd8c74d048e0de747b6 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 24 Jan 2025 10:57:41 -0800 Subject: [PATCH 11/14] Update changelog. --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 9c92bd15..fd032b02 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,7 @@ ## [Unreleased] ### Changed +- **BREAKING** remove RxJava completely in favor of Kotlin Flow - Bump required java from 11 to 17. ## [4.3.1] - 2024-07-05 From 655c0be23db737c15fb0f26fa7038528474a78c4 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 24 Jan 2025 11:09:57 -0800 Subject: [PATCH 12/14] We don't need `Rx.INSTANCE` any more. --- .../main/java/com/diffplug/common/swt/SwtRx.java | 15 +++++++-------- .../diffplug/common/swt/widgets/NoBorderBtn.java | 2 +- .../java/com/diffplug/common/swt/ShellsTest.java | 5 ++--- .../com/diffplug/common/swt/SwtExecProfile.java | 4 ++-- .../diffplug/common/swt/jface/ViewerMiscTest.java | 7 +++---- 5 files changed, 15 insertions(+), 18 deletions(-) diff --git a/durian-swt/src/main/java/com/diffplug/common/swt/SwtRx.java b/durian-swt/src/main/java/com/diffplug/common/swt/SwtRx.java index 1243314c..4212a772 100644 --- a/durian-swt/src/main/java/com/diffplug/common/swt/SwtRx.java +++ b/durian-swt/src/main/java/com/diffplug/common/swt/SwtRx.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2022 DiffPlug + * Copyright (C) 2020-2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,6 @@ */ package com.diffplug.common.swt; - import com.diffplug.common.base.Preconditions; import com.diffplug.common.collect.ImmutableList; import com.diffplug.common.primitives.Ints; @@ -51,19 +50,19 @@ public class SwtRx { /** Subscribes to the given widget and pipes the events to an {@link Flow}<{@link Event}>. */ public static @SwtThread Flow addListener(Widget widget, Stream events) { - MutableSharedFlow observable = Rx.INSTANCE.createEmitFlow(); + MutableSharedFlow observable = Rx.createEmitFlow(); events.forEach(event -> widget.addListener(event, e -> { - Rx.INSTANCE.emit(observable, e); + Rx.emit(observable, e); })); return observable; } /** Returns an {@link Flow}<{@link Point}> of the right-click mouse-up on the given control, in global coordinates. */ public static @SwtThread Flow rightClickGlobal(Control ctl) { - MutableSharedFlow observable = Rx.INSTANCE.createEmitFlow(); + MutableSharedFlow observable = Rx.createEmitFlow(); ctl.addListener(MouseClick.RIGHT_CLICK_EVENT, e -> { if (e.button == MouseClick.RIGHT.code()) { - Rx.INSTANCE.emit(observable, ctl.toDisplay(e.x, e.y)); + Rx.emit(observable, ctl.toDisplay(e.x, e.y)); } }); return observable; @@ -71,10 +70,10 @@ public class SwtRx { /** Returns an {@link Flow}<{@link Point}> of the right-click mouse-up on the given control, in local coordinates. */ public static @SwtThread Flow rightClickLocal(Control ctl) { - MutableSharedFlow observable = Rx.INSTANCE.createEmitFlow(); + MutableSharedFlow observable = Rx.createEmitFlow(); ctl.addListener(MouseClick.RIGHT_CLICK_EVENT, e -> { if (e.button == MouseClick.RIGHT.code()) { - Rx.INSTANCE.emit(observable, new Point(e.x, e.y)); + Rx.emit(observable, new Point(e.x, e.y)); } }); return observable; diff --git a/durian-swt/src/main/java/com/diffplug/common/swt/widgets/NoBorderBtn.java b/durian-swt/src/main/java/com/diffplug/common/swt/widgets/NoBorderBtn.java index 6dcc0865..8c8b2d2a 100644 --- a/durian-swt/src/main/java/com/diffplug/common/swt/widgets/NoBorderBtn.java +++ b/durian-swt/src/main/java/com/diffplug/common/swt/widgets/NoBorderBtn.java @@ -38,7 +38,7 @@ public class NoBorderBtn extends ControlWrapper.AroundControl { private Image img = null; private Rectangle imgBounds; private boolean enabled = true; - private MutableSharedFlow selection = Rx.INSTANCE.createEmitFlow(); + private MutableSharedFlow selection = Rx.createEmitFlow(); public NoBorderBtn(Composite parent) { super(new Canvas(parent, SWT.NONE)); diff --git a/durian-swt/src/test/java/com/diffplug/common/swt/ShellsTest.java b/durian-swt/src/test/java/com/diffplug/common/swt/ShellsTest.java index 098fae50..999d7e16 100644 --- a/durian-swt/src/test/java/com/diffplug/common/swt/ShellsTest.java +++ b/durian-swt/src/test/java/com/diffplug/common/swt/ShellsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 DiffPlug + * Copyright (C) 2020-2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,6 @@ */ package com.diffplug.common.swt; - import java.util.function.Consumer; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Point; @@ -27,7 +26,7 @@ @Category(InteractiveTest.class) public class ShellsTest { - private static final int UNIT = SwtMisc.systemFontWidth() * 20; + private static final int UNIT = SwtMisc.systemFontWidthTimes(20); @Test public void testPack() { diff --git a/durian-swt/src/test/java/com/diffplug/common/swt/SwtExecProfile.java b/durian-swt/src/test/java/com/diffplug/common/swt/SwtExecProfile.java index 56bd4e63..2a618850 100644 --- a/durian-swt/src/test/java/com/diffplug/common/swt/SwtExecProfile.java +++ b/durian-swt/src/test/java/com/diffplug/common/swt/SwtExecProfile.java @@ -131,7 +131,7 @@ public void addSwtExec(String name, SwtExec underTest) { public void run(Widget guard) { JuxtaProfiler profiler = new JuxtaProfiler(); profiler.addTestNanoWrap2Sec("control", () -> { - MutableSharedFlow subject = Rx.INSTANCE.createEmitFlow(); + MutableSharedFlow subject = Rx.createEmitFlow(); drain(subject); }); toProfile.forEach((name, underTest) -> { @@ -141,7 +141,7 @@ public void run(Widget guard) { @Override protected void init() throws Throwable { - subject = Rx.INSTANCE.createEmitFlow(); + subject = Rx.createEmitFlow(); sub = underTest.guardOn(guard).subscribeDisposable(subject, val -> {}); } diff --git a/durian-swt/src/test/java/com/diffplug/common/swt/jface/ViewerMiscTest.java b/durian-swt/src/test/java/com/diffplug/common/swt/jface/ViewerMiscTest.java index 65a3376c..9a0455b0 100644 --- a/durian-swt/src/test/java/com/diffplug/common/swt/jface/ViewerMiscTest.java +++ b/durian-swt/src/test/java/com/diffplug/common/swt/jface/ViewerMiscTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 DiffPlug + * Copyright (C) 2020-2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,6 @@ */ package com.diffplug.common.swt.jface; - import com.diffplug.common.base.Errors; import com.diffplug.common.base.StringPrinter; import com.diffplug.common.collect.ImmutableList; @@ -175,7 +174,7 @@ public void testLazyContentProviderFile() { .setLabelProviderText(File::getName); format.addColumn().setText("Last Modified") .setLabelProviderText(file -> new Date(file.lastModified()).toString()) - .setLayoutPixel(SwtMisc.systemFontWidth() * new Date().toString().length()); + .setLayoutPixel(SwtMisc.systemFontWidthTimes(new Date().toString())); // create the tree viewer TreeViewer viewer = format.buildTree(cmp); @@ -203,7 +202,7 @@ public void testLazyContentProviderPath() { .setLabelProviderText(path -> path.getFileName().toString()); format.addColumn().setText("Last Modified") .setLabelProviderText(path -> Errors.suppress().getWithDefault(() -> Files.getLastModifiedTime(path).toString(), "")) - .setLayoutPixel(SwtMisc.systemFontWidth() * new Date().toString().length()); + .setLayoutPixel(SwtMisc.systemFontWidthTimes(new Date().toString())); // create the tree viewer TreeViewer viewer = format.buildTree(cmp); From 53338fd212ab170842eafb1a1d0277c511a34be8 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 24 Jan 2025 22:56:07 -0800 Subject: [PATCH 13/14] Stop using composite build crutch. --- .github/workflows/ci.yml | 1 - gradle.properties | 2 +- settings.gradle | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1da20218..1d56c465 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - run: git clone https://github.com/diffplug/durian-rx ../durian-rx - name: Install JDK ${{ matrix.jre }} uses: actions/setup-java@v4 with: diff --git a/gradle.properties b/gradle.properties index ea7cb567..e3547083 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,7 +13,7 @@ VER_FINDBUGS=3.0.1 # Dependencies VER_DURIAN=1.2.0 -VER_DURIAN_RX=4.0.0 +VER_DURIAN_RX=5.0.0 VER_DURIAN_DEBUG=1.0.0 # SWT Dependencies from P2 SWT_VERSION=4.21 diff --git a/settings.gradle b/settings.gradle index 51e237f9..0eb06ca7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -39,8 +39,6 @@ blowdryerSetup { rootProject.name = 'durian-swt-root' -includeBuild '../durian-rx' - include 'durian-swt' include 'durian-swt.os' include 'durian-swt.cocoa.macosx.aarch64' From 01e6e6aba61dd63db56cfaee4118591b80a1a401 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 24 Jan 2025 10:59:23 -0800 Subject: [PATCH 14/14] SwtMisc.systemFontWidth` now returns `double` instead of `int`. --- CHANGES.md | 4 +++- .../java/com/diffplug/common/swt/SwtMisc.java | 23 ++++++++++++------- .../diffplug/common/swt/widgets/ScaleCtl.java | 5 ++-- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index fd032b02..d146e715 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,9 @@ ## [Unreleased] ### Changed -- **BREAKING** remove RxJava completely in favor of Kotlin Flow +- **BREAKING** `SwtMisc.systemFontWidth` now returns `double` instead of `int`. + - added new methods `systemFontWidthTimes(int)` and `systemFontWidthTimes(String)` to make this easier to deal with +- **BREAKING** remove RxJava completely in favor of Kotlin Flow. - Bump required java from 11 to 17. ## [4.3.1] - 2024-07-05 diff --git a/durian-swt/src/main/java/com/diffplug/common/swt/SwtMisc.java b/durian-swt/src/main/java/com/diffplug/common/swt/SwtMisc.java index 1d94ac8e..d3817e86 100644 --- a/durian-swt/src/main/java/com/diffplug/common/swt/SwtMisc.java +++ b/durian-swt/src/main/java/com/diffplug/common/swt/SwtMisc.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 DiffPlug + * Copyright (C) 2020-2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,6 @@ */ package com.diffplug.common.swt; - import com.diffplug.common.base.Box; import com.diffplug.common.base.Errors; import com.diffplug.common.base.Preconditions; @@ -358,7 +357,7 @@ public static int blockForMessageBox(String title, String message, int style) { /////////////////// /** The cached height of the system font. */ static int systemFontHeight = 0; - static int systemFontWidth = 0; + static double systemFontWidth = 0; /** Populates the height and width of the system font. */ private static void populateSystemFont() { @@ -368,7 +367,7 @@ private static void populateSystemFont() { FontMetrics metrics = gc.getFontMetrics(); systemFontHeight = metrics.getHeight(); - systemFontWidth = metrics.getAverageCharWidth(); + systemFontWidth = metrics.getAverageCharacterWidth(); if (OS.getNative().isMac()) { // add 20% width on Mac systemFontWidth = (systemFontWidth * 12) / 10; @@ -387,13 +386,21 @@ public static int systemFontHeight() { } /** Returns the width of the system font. */ - public static int systemFontWidth() { + public static double systemFontWidth() { if (systemFontWidth == 0) { populateSystemFont(); } return systemFontWidth; } + public static int systemFontWidthTimes(int numChars) { + return (int) Math.round(systemFontWidth() * numChars); + } + + public static int systemFontWidthTimes(String str) { + return systemFontWidthTimes(str.length()); + } + /** Returns a distance which is a snug fit for a line of text in the system font. */ public static int systemFontSnug() { return systemFontHeight() + Layouts.defaultMargin(); @@ -401,12 +408,12 @@ public static int systemFontSnug() { /** Returns the default width of a button, scaled for the system font. */ public static int defaultButtonWidth() { - return systemFontWidth() * " Cancel ".length(); + return systemFontWidthTimes(" Cancel "); } /** Returns the default width of a dialog. */ public static int defaultDialogWidth() { - return 50 * systemFontWidth(); + return systemFontWidthTimes(50); } /** Returns a size which is scaled by the system font's height. */ @@ -421,7 +428,7 @@ public static int scaleByFontHeight(int rows) { /** Returns a point that represents the size of a (cols x rows) grid of characters printed in the standard system font. */ public static Point scaleByFont(int cols, int rows) { - return new Point(cols * systemFontWidth(), rows * systemFontHeight()); + return new Point(systemFontWidthTimes(cols), rows * systemFontHeight()); } /** Returns a dimension which is guaranteed to be comfortable for the given string. */ diff --git a/durian-swt/src/main/java/com/diffplug/common/swt/widgets/ScaleCtl.java b/durian-swt/src/main/java/com/diffplug/common/swt/widgets/ScaleCtl.java index 24677bf7..79567e36 100644 --- a/durian-swt/src/main/java/com/diffplug/common/swt/widgets/ScaleCtl.java +++ b/durian-swt/src/main/java/com/diffplug/common/swt/widgets/ScaleCtl.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 DiffPlug + * Copyright (C) 2020-2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,6 @@ */ package com.diffplug.common.swt.widgets; - import com.diffplug.common.base.Preconditions; import com.diffplug.common.rx.RxBox; import com.diffplug.common.swt.ControlWrapper; @@ -136,7 +135,7 @@ public ScaleCtl(Composite parent, Builder builder, RxBox box) { } text = new Text(wrapped, SWT.BORDER | SWT.SINGLE); - Layouts.setGridData(text).grabHorizontal().widthHint(SwtMisc.systemFontWidth() * 4); + Layouts.setGridData(text).grabHorizontal().widthHint(SwtMisc.systemFontWidthTimes(4)); SwtExec.async().guardOn(text).execute(text::selectAll); Listener selectAllListener = SwtMisc.asListener(SwtExec.async().guardOn(text).wrap(text::selectAll));