Is there any way to bind a property to appConfig in tornadofx? - kotlin

Suppose I want to save a view's height and width value using appConfig in tornadofx. Is there anyway I can bind these properties to appConfig, so that when I save the config, the latest value of height and width will always be saved?

If what you want to do is to save the current width/height of the Window and restore that when the View is docked again, you can override onDock to do both operations there:
override fun onDock() {
if (config["w"] != null && config["h"] != null) {
currentWindow?.apply {
width = config.double("w")!!
height = config.double("h")!!
}
}
currentWindow?.apply {
Bindings.add(widthProperty(), heightProperty()).onChange {
with (config) {
put("w", width.toString())
put("h", height.toString())
save()
}
}
}
}

Related

SwiftUI ScrollView to PDF

Currently, I am using this code to create a PDF, but it only creates a PDF of the part of the page the user is looking at, and not the entire scroll view. How can I create a multi-page PDF to get the entire scroll view?
func exportToPDF(width:CGFloat, height:CGFloat, airplane: String, completion: #escaping (String) -> Void) {
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let outputFileURL = documentDirectory.appendingPathComponent("smartsheet-\(airplane.afterchar(first: "N")).pdf")
//Normal with
let width: CGFloat = width
//Estimate the height of your view
let height: CGFloat = height
let charts = SmartSheet().environmentObject(Variables())
let pdfVC = UIHostingController(rootView: charts)
pdfVC.view.frame = CGRect(x: 0, y: 0, width: width, height: height)
//Render the view behind all other views
let rootVC = UIApplication.shared.windows.first?.rootViewController
rootVC?.addChild(pdfVC)
rootVC?.view.insertSubview(pdfVC.view, at: 0)
//Render the PDF
let pdfRenderer = UIGraphicsPDFRenderer(bounds: CGRect(x: 0, y: 0, width: width, height: height))
DispatchQueue.main.async {
do {
try pdfRenderer.writePDF(to: outputFileURL, withActions: { (context) in
context.beginPage()
rootVC?.view.layer.render(in: context.cgContext)
})
UserDefaults.standard.set(outputFileURL, forKey: "pdf")
UserDefaults.standard.synchronize()
print("wrote file to: \(outputFileURL.path)")
completion(outputFileURL.path)
} catch {
print("Could not create PDF file: \(error.localizedDescription)")
}
pdfVC.removeFromParent()
pdfVC.view.removeFromSuperview()
}
}
I just posted this answer to another question as well.
https://stackoverflow.com/a/74033480/14076779
After attempting a number of solutions to creating a pdf from a SwiftUI ScrollView and landed on this implementation. It is by no means an implementation that is pretty, but it seems to work.
M use case is that I present a modal to capture a user's signature and when the signature is collected, the content of the current screen is captured. There is no pagination, just a capture of the content currently rendered to the width of the screen and as much length as is required by the view.
The content consists of an array of cards that are dynamically generated, so the view has no awareness of the content and cannot calculate the height. I have not tested on a list, but I imagine it would provide similar results.
struct SummaryTabView: View {
// MARK: View Model
#ObservedObject var viewModel: SummaryTabViewModel
// MARK: States and Bindings
#State private var contentSize: CGSize = .zero // Updated when the size preference key for the overlay changes
#State private var contentSizeForPDF: CGSize = .zero // Set to match the content size when the signature modal is displayed.
// MARK: Initialization
init(viewModel: SummaryTabViewModel) {
self.viewModel = viewModel
}
// MARK: View Body
var body: some View {
ScrollView {
content
.background(Color.gray)
// Placing the a geometry reader in the overlay here seems to work better than doing so in a background and updates the size value for the size preference key.
.overlay(
GeometryReader { proxy in
Color.clear.preference(key: SizePreferenceKey.self,
value: proxy.size)
}
)
}
.onPreferenceChange(SizePreferenceKey.self) {
// This keeps the content size up-to-date during changes to the content including rotating.
contentSize = $0
}
// There are issues with the sizing the content when the orientation changes and a modal view is open such as when collecting the signature. If the iPad is rotated when the modal is open, then the content size is not being set properly. To avoid this, when the user opens the modal for signing, the content size for PDF generation is set to the current value since the signature placeholder is already present so the content size will not change.
.onReceive(NotificationCenter.default.publisher(for: .openingSignatureModal)) { _ in
self.contentSizeForPDF = contentSize
}
}
// MARK: Subviews
var header: SceneHeaderView {
SceneHeaderView(viewModel: viewModel)
}
// Extracting this view into a variable allows grabbing the screenshot when the customer signature is collected.
var content: some View {
LazyVStack(spacing: 16) {
header
ForEach(viewModel.cardViewModels) { cardViewModel in
// Each card needs to be in a VStack with no spacing or it will end up having a gap between the header and the card body.
VStack(spacing: 0) {
UICardView(viewModel: cardViewModel)
}
}
}
.onReceive(viewModel.signaturePublisher) {
saveSnapshot(content: content, size: contentSizeForPDF)
}
}
// MARK: View Functions
/// Creates and saves a pdf data file to the inspection's report property. This is done asynchronously to avoid odd behavior with the graphics renering process.
private func saveSnapshot<Content: View>(content: Content, size: CGSize) {
let pdf = content
.frame(width: size.width, height: size.height)
.padding(.bottom, .standardEdgeSpacing) // Not sure why this padding needs to be added but for some reason without it the pdf is tight to the last card on the bottom. It appears that extra padding is being added above the header, but not sure why.
.toPDF(format: self.pdfFormat)
self.saveReport(data: pdf)
}
private var pdfFormat: UIGraphicsPDFRendererFormat {
let format = UIGraphicsPDFRendererFormat()
// TODO: Add Author here
let metadata = [kCGPDFContextCreator: "Mike",
kCGPDFContextAuthor: viewModel.userService.userFullName]
format.documentInfo = metadata as Dictionary<String, Any>
return format
}
private func saveReport(data: Data?) {
guard let data = data else {
log.error("No data generated for pdf.")
return
}
do {
try viewModel.saveReport(data)
print("Report generated for inspection")
} catch {
print("Failed to create pdf report due to error: \(error)")
}
// Used to verify report in simulator
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
var filename = "\(UUID().uuidString.prefix(6)).pdf"
let bodyPath = documentDirectory.appendingPathComponent(filename)
do {
let value: Data? = viewModel.savedReport
try value?.write(to: bodyPath)
print("pdf directory: \(documentDirectory)")
} catch {
print("report not generated")
}
}
// MARK: Size Preference Key
private struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}
}
// MARK: Get image from SwiftUI View
extension View {
func snapshot() -> UIImage {
let controller = UIHostingController(rootView: self)
let view = controller.view
let targetSize = controller.view.intrinsicContentSize
view?.bounds = CGRect(origin: .zero, size: targetSize)
view?.backgroundColor = .clear
let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
func toPDF(format: UIGraphicsPDFRendererFormat) -> Data {
let controller = UIHostingController(rootView: self)
let view = controller.view
let contentSize = controller.view.intrinsicContentSize
view?.bounds = CGRect(origin: .zero, size: contentSize)
view?.backgroundColor = .clear
let renderer = UIGraphicsPDFRenderer(bounds: controller.view.bounds, format: format)
return renderer.pdfData { context in
context.beginPage()
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
}
I landed on using a size preference key because a lot of other options did not perform as expected. For instance, setting the content size in .onAppear ended up yielding a pdf with a lot of space above and below it. At one point I was creating the pdf using a boolean state that when changed would use the geometry reader's current geometry. That generally worked, but would provide unexpected results when the user would rotate the device while a modal was open.
References (as best I can recall):
SizePreferenceKey: https://swiftwithmajid.com/2020/01/15/the-magic-of-view-preferences-in-swiftui/
Snapshot View extension: https://www.hackingwithswift.com/quick-start/swiftui/how-to-convert-a-swiftui-view-to-an-image

How to update the map properly using canvas and a button when the players relocate to a new location?

My game snapshot Attachment> Blockquote
My canvas constructs
public class DrawingView4 extends View{
DrawingView4(Context context4)
{
super(context4);
}
#Override protected void onDraw(Canvas canvas4)
{
int tile4=0;
if (DoneDrawFacilities && doneloading) {
}
else {
for (int i4=0; i4< 17 && notfixingorientation; i4++){
if (i4< 16) {
for (int ii4=0; ii4 < 16; ii4++){
try {
//Checking the data of all spots of the game map from package directory.
//Then Draw in canvas if the data of the spot occupied by type of human and core facilities,
FacilityList = new Gson().fromJson(FileUtil.readFile(FileUtil.getPackageDataDir(getApplicationContext()).concat("/GameResource/Tile".concat(String.valueOf((long)(tile4 + 1)).concat(".data")))), new TypeToken<ArrayList<HashMap<String, Object>>>(){}.getType());
if (FacilityList.get((int)0).get("Type").toString().equals("Human")) {
canvas4.drawBitmap(BitmapFactory.decodeFile(AllObjects.getString(String.valueOf((long)(tile4)), "")),null,new Rect(ii4*120, i4*120, 120*(ii4+1),120*(i4+1)), null);
}
if (FacilityList.get((int)0).get("Type").toString().equals("Core")) {
if (FacilityList.get((int)0).get("Name").toString().equals("Arena")) {
canvas4.drawBitmap(BitmapFactory.decodeFile(AllObjects.getString(String.valueOf((long)(tile4)), "")),null,new Rect(7*120, 7*120, 120*(8+1),120*(8+1)), null);
}
else {
}
}
} catch (Exception e) {
}
FacilityList.clear();
tile4++;
}
}
else {
DoneDrawFacilities = true;
}
}
}
}}
Blockquote
My Relocate button
//I use sharepreference called AllObjects rather than a list and I Only update the path of objects in specific tile in sharedpreference such as in variable 1, 2, etc. and then re-draw using this code below, next time I update the objects path and get this object path from the same json file in the package directory. Too decode to Bitmap and then Draw in canvas.
//Some other code are removed that just updating some data in specific file directory.
AA_structures_facilities.removeAllViews();
AA_structures_facilities.addView(new DrawingView4(GameActivity.this));
// But It freezes the screen or stop me from touching the touch event in a second everytime I update new canvas.
//WHILE MY touchevent is hundled in the parent LinearLayout where the canvas is placed.

Change dynamically border color of an agent in Repast Symphony

I have to change dynamically the border color of an agent. The agent is represented as the default circle on the display. The displayed color has to change according to a boolean variable defined inside the agent Class.
When the agent is created and displayed for the first time, it has the correct style but when the boolen variable inside the agent class changes, the border color does not change.
If I do the same for the fill color of the agent instead, it works fine. I put here the code I used:
public class NodeStyle extends DefaultStyleOGL2D{
#Override
public Color getBorderColor(Object agent) {
Color borderColor = Color.BLACK;
if(agent instanceof Process) {
Process p = (Process)agent;
if(p.isParticularNode) {
borderColor = Color.RED;
}
}
return borderColor;
}
}
When the agent is created and it is added to the context, it take the correct color but if isParticularNode changes, the border color does not change.
I've tryed also to do the same importing the interface StyleOGL2D but the problem remains
I tried this with the JZombies demo, adding an "id" double to each zombie that is set with RandomHelper.nextDouble() each tick. The border color changes as expected. By default the border size is 0, so perhaps that needs to be changed in your code.
public class ZombieStyle extends DefaultStyleOGL2D {
public Color getColor(Object agent) {
return Color.RED;
}
public Color getBorderColor(Object agent) {
Zombie z = (Zombie)agent;
if (z.getID() > 0.5) {
return Color.GREEN;
}
return Color.black;
}
public int getBorderSize(Object agent) {
return 4;
}
}

How to switch textFieldStyle to a TextField when user taps the field?

I'm trying to change the TextField style from .plain to .roundedCorners when the user taps on the TextField.
The TextField itself is initially disabled (.plain style) and when the user taps on it, should enable editing mode (which is working) and change to (.roundedCorners style)
I've tried changing the style based on TextField state (if disabled ? .plain : .roundedCorners), but that doesn't seem to be working
.textFieldStyle(self.listState.editingScreenshot == nil ? .plain : .roundedCorners)
I get the following error when using inline if statement:
Type 'StaticMember' has no member
'roundedCorners'.
Using style conditionally may be challenging, I prefer this approach, which is also much more customizable:
In this example I use a darker border color depending on activation, and in the second example, I just remove the style completely:
struct ContentView: View {
#State private var active1: Bool = false
#State private var value1 = ""
#State private var active2: Bool = false
#State private var value2 = ""
var body: some View {
VStack(alignment: .leading) {
Spacer()
Text("Field 1")
TextField("", text: $value1, onEditingChanged: { self.active1 = $0 }).padding().overlay(TextFieldBorder(rounded: active1))
Text("Field 2")
TextField("", text: $value2, onEditingChanged: { self.active2 = $0 }).padding().overlay(TextFieldBorder(rounded: active2))
Spacer()
}.background(Color(white: 0.9))
}
}
struct TextFieldBorder: View {
var rounded = true
var body: some View {
Group {
if rounded {
RoundedRectangle(cornerRadius: 10).stroke(Color.black)
} else {
RoundedRectangle(cornerRadius: 10).stroke(Color.gray)
}
}
}
}
To remove the style completely:
struct TextFieldBorder: View {
var rounded = true
var body: some View {
Group {
if rounded {
RoundedRectangle(cornerRadius: 10).stroke(Color.black)
} else {
RoundedRectangle(cornerRadius: 10).stroke(Color.clear)
}
}
}
}
The static member you are looking for is roundedBorder:
public static var roundedBorder: RoundedBorderTextFieldStyle.Member { get }

How to Color TableViewer Rows alternatively

I'm using Viewer Framework in my rcp application, i would like to color viewer rows alternatively,i tried to override getBackground method of ColumnLabelProvider, below is code snippet
col.setLabelProvider(new ColumnLabelProvider(){
----//other methods
#override
public Color getBackground(Object element) {
return gray;//here gray is color object defined somewhere in class
}
});
this colors the columns, but not a row, below is output
how do i achieve this correctly
You can find an example here which uses an IColorProvider. Maybe you could just reuse the getBackground() method in your code, just change the reference to your tableViewer:
public Color getBackground(Object element) {
ArrayList list = (ArrayList) tableViewer.getInput();
int index = list.indexOf(element);
if ((index % 2) == 0) {
return gray;
} else {
return null;
}
}