Commit 302bde05 authored by David's avatar David

Works on the desktop!

parent f48e1e16
package `is`.kow.deskscreen.camera
import `is`.kow.deskscreen.MainController
import `is`.kow.deskscreen.TransitionsController
import com.github.thomasnield.rxkotlinfx.doOnNextFx
import com.github.thomasnield.rxkotlinfx.observeOnFx
import com.hopding.jrpicam.RPiCamera
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
import javafx.embed.swing.SwingFXUtils
import javafx.event.ActionEvent
import javafx.scene.image.Image
import mu.KotlinLogging
import tornadofx.*
import java.nio.file.Paths
import java.util.concurrent.TimeUnit
class CameraController : Controller() {
......@@ -19,6 +23,7 @@ class CameraController : Controller() {
private val mainController: MainController by inject()
private val cameraStateModel: CameraStateModel by inject()
private val transitionsController: TransitionsController by inject()
init {
//Only care that the folders exist
......@@ -50,35 +55,85 @@ class CameraController : Controller() {
val timeout = 5
//Handles the timer countdown
Observable.range(timeout, 0)
//TODO: why did this never show up?
logger.debug("Right before starting range timeout")
Observable.intervalRange(0, timeout.toLong() + 1, 0, 1, TimeUnit.SECONDS)
.map {
timeout - it
}
.observeOnFx()
.map {
logger.debug("COUNTING: $it")
cameraStateModel.countdownPercentage.value = it / timeout.toDouble()
}
.subscribe()
//The camera blocks things. need to also start the countdown observable for the thing.
//TODO: when it's not the pi, just use some image with the same dimensions!
camera
?.setRotation(270)
?.setDateTimeOn()
?.setTimeout(timeout)
?.setFullPreviewOn()
?.takeBufferedStill()
if (mainController.isPi) {
//Camera won't be null if it's pi!
val bf = camera
?.setRotation(270)
?.setDateTimeOn()
?.setTimeout(timeout)
?.setFullPreviewOn()
?.takeBufferedStill()!!
//lets just convert it directly
SwingFXUtils.toFXImage(bf, null)
} else {
//load the cat picture WHY WON'T YOU LOAD MY KITTY
val stream = this.javaClass.getResourceAsStream("/imagery/MaineCoon-cropped.jpeg")
val img = Image(stream)
Thread.sleep((timeout * 1000).toLong())
img
}
}
.doOnNextFx {
cameraStateModel.image.value = it
logger.debug("SETTING IMAGE IN THE MODEL AND CHANGING STATE")
cameraStateModel.image.set(it)
cameraStateModel.state.set(PictureTakingState.PREVIEWING)
}
.doOnError {
logger.error("Error during taking picture!", it)
}
.subscribe()
}
fun keepPicture() {
fun keepPicture(actionEvents: Observable<ActionEvent>) {
actionEvents
.doOnNext {
logger.error("SHARE PICTURE OR SOMETHING!")
}
.observeOnFx()
.map {
cameraStateModel.image.value = null
cameraStateModel.state.set(PictureTakingState.READY)
//TODO: would set state to sharing or something to display what to do with the picture
//Until then, just return them to the main frame
transitionsController.showDefaultView()
}
.subscribe()
}
fun rejectPicture(actionEvents: Observable<ActionEvent>) {
actionEvents
.observeOnFx()
.map {
cameraStateModel.image.value = null
cameraStateModel.state.set(PictureTakingState.READY)
}
.subscribe()
}
fun rejectPicture() {
fun cancelPictureTaking(actionEvents: Observable<ActionEvent>) {
actionEvents
.observeOnFx()
.doOnNext {
cameraStateModel.image.value = null
cameraStateModel.state.set(PictureTakingState.READY)
transitionsController.showDefaultView()
}
.subscribe()
}
}
\ No newline at end of file
package `is`.kow.deskscreen.camera
import com.github.thomasnield.rxkotlinfx.actionEvents
import com.github.thomasnield.rxkotlinfx.toObservableChanges
import com.github.thomasnield.rxkotlinfx.toObservableChangesNonNull
import javafx.beans.binding.Bindings
import javafx.beans.binding.BooleanBinding
import javafx.beans.property.SimpleDoubleProperty
import javafx.beans.property.SimpleObjectProperty
import javafx.geometry.Pos
import javafx.scene.control.ProgressBar
import javafx.scene.image.Image
import javafx.scene.layout.Priority
import mu.KotlinLogging
import org.kordamp.ikonli.fontawesome5.FontAwesomeSolid
import org.kordamp.ikonli.javafx.FontIcon
import tornadofx.*
import java.awt.image.BufferedImage
import java.util.concurrent.atomic.AtomicReference
enum class PictureTakingState {
READY, TAKING, PREVIEWING, GOOD
}
class CameraState {
val imageProperty = SimpleObjectProperty<BufferedImage>(this, "image", null)
val imageProperty = SimpleObjectProperty<Image>(this, "image", null)
var image by imageProperty
val stateProperty = SimpleObjectProperty<PictureTakingState>(this, "state", PictureTakingState.READY)
......@@ -32,34 +35,50 @@ class CameraState {
class CameraStateModel : ItemViewModel<CameraState>() {
private val logger = KotlinLogging.logger {}
val image: SimpleObjectProperty<BufferedImage> = bind(CameraState::imageProperty)
val image: SimpleObjectProperty<Image> = bind(CameraState::imageProperty)
val state: SimpleObjectProperty<PictureTakingState> = bind(CameraState::stateProperty)
val countdownPercentage = bind(CameraState::countdownPercentageProperty)
val previewing: BooleanBinding = Bindings.equal(PictureTakingState.PREVIEWING, state)
val readyToTake: BooleanBinding = Bindings.equal(PictureTakingState.READY, state)
val taking: BooleanBinding = Bindings.equal(PictureTakingState.TAKING, state)
}
class CameraView : View() {
private val logger = KotlinLogging.logger {}
private val cameraLoadingView: CameraLoadingView by inject()
private val cameraImagePreviewView: CameraImagePreviewView by inject()
private val cameraStateModel: CameraStateModel by inject()
private val cameraController: CameraController by inject()
private val currentView = AtomicReference<View>()
init {
//set up the state, it's weird that I even have to do this, the initial value doesn't work
cameraStateModel.state.set(PictureTakingState.READY)
currentView.set(cameraLoadingView)
}
override fun onDock() {
//Hook up an observable to the state to have it swap views when ready
cameraStateModel.state.toObservableChangesNonNull()
.map { change ->
val to = change.newVal
to
}
.map {
when (it) {
PictureTakingState.PREVIEWING -> currentView.getAndSet(cameraImagePreviewView).replaceWith(cameraImagePreviewView)
else -> currentView.getAndSet(cameraLoadingView).replaceWith(cameraLoadingView)
}
}
.subscribe()
}
override val root = borderpane {
//Left and right can be 160px max
center = cameraLoadingView.root
center = currentView.get().root
right = vbox {
prefWidth = 160.0
......@@ -67,44 +86,78 @@ class CameraView : View() {
alignment = Pos.CENTER
button("", FontIcon.of(FontAwesomeSolid.CHECK_SQUARE, 60)) {
//WHYYYYY
style {
baseColor = c("black")
backgroundColor += c("green")
}
enableWhen {
cameraStateModel.previewing
}
cameraController.takePicture(actionEvents())
cameraController.keepPicture(actionEvents())
//Picture is good, enabled only after a capture
//Make it green
}
}
//Kinda want a space in-between these two
button("Take Picture!") {
//Call the controller action that updates the model and starts the count down
enableWhen(cameraStateModel.readyToTake)
hbox {
alignment = Pos.CENTER
style {
paddingTop = 10.0
}
button("Take Picture!") {
//Call the controller action that updates the model and starts the count down
enableWhen(cameraStateModel.readyToTake)
cameraController.takePicture(actionEvents())
}
}
label("Delay to take picture")
hbox {
progressbar {
bind(cameraStateModel.countdownPercentage)
//Set max width to the same as something?
hgrow = Priority.ALWAYS
progressbar(cameraStateModel.countdownPercentage) {
useMaxWidth = true
prefWidthProperty().bind(this@hbox.widthProperty())
}
}
}
left = vbox {
left = borderpane {
prefWidth = 160.0
hbox {
top = hbox {
alignment = Pos.CENTER
button("", FontIcon.of(FontAwesomeSolid.WINDOW_CLOSE, 60)) {
cameraController.rejectPicture(actionEvents())
style {
baseColor = c("black")
backgroundColor += c("red")
}
//Picture is no good, don't keep it
enableWhen {
cameraStateModel.previewing
}
}
}
bottom = hbox {
alignment = Pos.CENTER
button("", FontIcon.of(FontAwesomeSolid.EJECT, 60)) {
cameraController.cancelPictureTaking(actionEvents())
style {
baseColor = c("black")
}
disableWhen {
cameraStateModel.taking
}
}
}
}
}
}
......@@ -120,3 +173,28 @@ class CameraLoadingView : View() {
}
}
}
class CameraImagePreviewView : View() {
private val cameraStateModel: CameraStateModel by inject()
private val logger = KotlinLogging.logger {}
override val root = hbox {
//Put teh image here
imageview {
this.fitHeight = 480.0
this.fitWidth = 480.0
this.image = cameraStateModel.image.get()
//Why doesn't this get triggered, because first time through it's a race condition
cameraStateModel.image.toObservableChanges()
.map {
if (it.newVal != null) {
this.image = it.newVal
} else {
//Can this even work?
logger.debug("NO IMAGE")
this.image = null
}
}
.subscribe()
}
}
}
......@@ -176,7 +176,7 @@ class WeatherController : Controller() {
//TODO: emit to the ticker the error to display it on the screen
logger.error("Failed to acquire hourly forecast url", throwable)
}
.retry() //TODO: should have some kind of backoff or whatever
.retry() //TODO: should have some kind of backoff or whatever yeah need that
.subscribe()
//EMIT!
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment