Opening 2 identical windows simultaneously from one set of code - user-interface

I am using following code to create and show a window with GUI components as label, entry and button:
// modified from: https://github.com/andlabs/ui/wiki/Getting-Started
package main
import ("github.com/andlabs/ui")
func makewinfn() {
var name = ui.NewEntry()
var button = ui.NewButton("Greet")
var greeting = ui.NewLabel("")
box := ui.NewVerticalBox()
box.Append(ui.NewLabel("Enter your name:"), false)
box.Append(name, false)
box.Append(button, false)
box.Append(greeting, false)
mywindow := ui.NewWindow("MyTitle", 200, 100, false)
mywindow.SetChild(box)
button.OnClicked( func (*ui.Button) {greeting.SetText("Hello, " + name.Text() + "!") } )
mywindow.OnClosing( func (*ui.Window) bool { ui.Quit(); return true } )
mywindow.Show()
}
func main() {
ui.Main(makewinfn)
// HOW TO CREATE AND SHOW ANOTHER SUCH WINDOW HERE ?
// ui.Main(makewinfn) // this opens window only after first is closed.
}
It works well, but as I mentioned in title and as commented in code above, how can I open two such windows simultaneously from main function?
Repeating ui.Main(makewinfn) in main function leads to second window opening only after first is closed.
Thanks for your help.

In UI libraries usually the components or widgets you build the interface with have parents, and usually a single component may have at most one parent.
So if you want 2 windows, having the same components, you still have to create those components in 2 instances, because a component cannot be added to 2 different parents (in 2 different windows).
So simplest would be to move the component and window creation logic into a function, and call that twice from the function you pass to ui.Main():
func createWindow() {
var name = ui.NewEntry()
var button = ui.NewButton("Greet")
var greeting = ui.NewLabel("")
box := ui.NewVerticalBox()
box.Append(ui.NewLabel("Enter your name:"), false)
box.Append(name, false)
box.Append(button, false)
box.Append(greeting, false)
mywindow := ui.NewWindow("MyTitle", 200, 100, false)
mywindow.SetChild(box)
button.OnClicked( func (*ui.Button) {greeting.SetText("Hello, " + name.Text() + "!") } )
mywindow.OnClosing( func (*ui.Window) bool { ui.Quit(); return true } )
mywindow.Show()
}
func makewinfn() {
createWindow()
createWindow()
}
Using this createWindow() function of course is not a requirement, you could have a loop in makewinfn() with 2 iterations, each which could create a window.
The above example creates 2 identical windows, but they will be "independent". If you enter a text in one of them and click on its button, the result will only be seen in its containing / parent window. This is possible because each component has been created twice.
If you wan to customize the windows, you could pass a parameter to createWindow() so the window and its content could be customized / personalized based on its value. For example:
func createWindow(id string) {
var name = ui.NewEntry()
var button = ui.NewButton("Greet " + id)
var greeting = ui.NewLabel("")
box := ui.NewVerticalBox()
box.Append(ui.NewLabel("Enter your name " + id + ":"), false)
box.Append(name, false)
box.Append(button, false)
box.Append(greeting, false)
mywindow := ui.NewWindow("MyTitle " + id, 200, 100, false)
mywindow.SetChild(box)
button.OnClicked( func (*ui.Button) {greeting.SetText("Hello, " + name.Text() + "!") } )
mywindow.OnClosing( func (*ui.Window) bool { ui.Quit(); return true } )
mywindow.Show()
}
func makewinfn() {
createWindow("one")
createWindow("two")
}

I don't know Go but assuming the GUI works like any other language I've used, in your makewinfn function you can simply create more windows by calling ui.NewWindow() again.
func makewinfn() {
var name = ui.NewEntry()
var button = ui.NewButton("Greet")
var greeting = ui.NewLabel("")
box := ui.NewVerticalBox()
box.Append(ui.NewLabel("Enter your name:"), false)
box.Append(name, false)
box.Append(button, false)
box.Append(greeting, false)
mywindow := ui.NewWindow("MyTitle", 200, 100, false)
mywindow.SetChild(box)
button.OnClicked( func (*ui.Button) {greeting.SetText("Hello, " + name.Text() + "!") } )
mywindow.OnClosing( func (*ui.Window) bool { ui.Quit(); return true } )
mywindow.Show()
myOtherWindow := ui.NewWindow("MyOtherTitle", 200, 100, false)
myOtherWindow.Show()
}

Related

In SwiftUI how do I animate changes one at a time when they occur in a called method?

Although I get an animation when I tap the button, it's not the animation I want.
The entire view is being replaced at once, but I want to see each element change in sequence. I tried in both the parent view and in the called method. Neither produces the desired result.
(this is a simplified version of the original code)
import SwiftUI
struct SequencedCell: Identifiable {
let id = UUID()
var value: Int
mutating func addOne() {
value += 1
}
}
struct AQTwo: View {
#State var cells: [SequencedCell]
init() {
_cells = State(initialValue: (0 ..< 12).map { SequencedCell(value: $0) })
}
var body: some View {
VStack {
Spacer()
Button("+") {
sequencingMethod(items: $cells)
}
.font(.largeTitle)
Spacer()
HStack {
ForEach(Array(cells.enumerated()), id: \.1.id) { index, item in
// withAnimation(.linear(duration: 4)) {
Text("\(item.value)").tag(index)
// }
}
}
Spacer()
}
}
func sequencingMethod(items: Binding<[SequencedCell]>) {
for cell in items {
withAnimation(.linear(duration: 4)) {
cell.wrappedValue = SequencedCell(value: cell.wrappedValue.value + 1)
// cell.wrappedValue.addOne()
}
}
}
}
struct AQTwoPreview: PreviewProvider {
static var previews: some View {
AQTwo()
}
}
So I want the 0 to turn into a 1, the 1 then turn into a 2, etc.
Edit:
Even though I have accepted an answer, it answered my question, but didn't solve my issue.
I can't use DispatchQueue.main.asyncAfter because the value I am updating is an inout parameter and it makes the compiler unhappy:
Escaping closure captures 'inout' parameter 'grid'
So I tried Malcolm's (malhal) suggestion to use delay, but everything happens immediately with no sequential animation (the entire block of updated items animate as one)
Here's the recursive method I am calling:
static func recursiveAlgorithm(targetFill fillValue: Int, in grid: inout [[CellItem]],
at point: (x: Int, y: Int), originalFill: Int? = nil, delay: TimeInterval) -> [[CellItem]] {
/// make sure the point is on the board (or return)
guard isValidPlacement(point) else { return grid }
/// the first time this is called we don't have `originalFill`
/// so we read it from the starting point
let tick = delay + 0.2
//AnimationTimer.shared.tick()
let startValue = originalFill ?? grid[point.x][point.y].value
if grid[point.x][point.y].value == startValue {
withAnimation(.linear(duration: 0.1).delay(tick)) {
grid[point.x][point.y].value = fillValue
}
_ = recursiveAlgorithm(targetFill: fillValue, in: &grid, at: (point.x, point.y - 1), originalFill: startValue, delay: tick)
_ = recursiveAlgorithm(targetFill: fillValue, in: &grid, at: (point.x, point.y + 1), originalFill: startValue, delay: tick)
_ = recursiveAlgorithm(targetFill: fillValue, in: &grid, at: (point.x - 1, point.y), originalFill: startValue, delay: tick)
_ = recursiveAlgorithm(targetFill: fillValue, in: &grid, at: (point.x + 1, point.y), originalFill: startValue, delay: tick)
}
return grid
}
Further comments/suggestions are welcome, as I continue to wrestle with this.
As mentioned in the comments, the lowest-tech version is probably just using a DisatpchQueue.main.asyncAfter call:
func sequencingMethod(items: Binding<[SequencedCell]>) {
var wait: TimeInterval = 0.0
for cell in items {
DispatchQueue.main.asyncAfter(deadline: .now() + wait) {
withAnimation(.linear(duration: 1)) {
cell.wrappedValue = SequencedCell(value: cell.wrappedValue.value + 1)
}
}
wait += 1.0
}
}
You could use delay(_:) for that, e.g.
func sequencingMethod(items: Binding<[SequencedCell]>) {
var delayDuration = 0.0
for cell in items {
withAnimation(.linear(duration: 4).delay(delayDuration)) {
cell.wrappedValue = SequencedCell(value: cell.wrappedValue.value + 1)
}
delayDuration += 0.5
}
}

How to create a dynamic calculation based off more than 1 State

I am brand new to Swift (and coding in general). I am working on an app that will output a calculation based off the tracking of two states. The two states are brewModel and waterAmount. I am able to successfully create a function that will return one calculation based on the two states. However, now I am trying to create a Picker that will toggle the calculation between two measurements - grams and tablespoons. This is where I am having trouble.
I tried to write a series of conditionals in different ways such as if and else if as well as switch cases, but it doesn't work. When I build the simulator, Xcode will just think for a long time until I stop it. Sometimes I get error messages after I manually stop it and sometimes I don't. Today I got "Command CompileSwiftSources failed with a nonzero exit code."
Does anyone have any suggestions?
Also, I apologize if my code is messy, I have a bunch of things commented out that I am playing with. The func computeGrinds does work but just for the one calculation. Thank you!
import SwiftUI
struct Water: View {
// #EnvironmentObject var favorites: Favorites
#State var animationInProgress = true
#State var brewModel: BrewModel
#State var waterAmount: Int = 1
#State var grindsSelection = "tbsp"
var grindOptions = ["tbsp", "grams"]
// var resultGrindCalc: Double {
//
// var value = Double(0)
// }
// switch grindsSelection {
// case "tbsp" || brewModel.frenchPress:
// value = Double(waterAmount) * 2.5
//
// }
//
// func computeGrinds () -> Double {
// switch brewModel {
// case .frenchPress, .chemex:
// return (2.5 * Double(waterAmount))
// case .drip :
// return Double(2 * Double(waterAmount))
// case .mokaPot:
// return Double(1 * Double(waterAmount))
// case .aeroPress:
// return Double(1.6 * Double(waterAmount))
// // default:
// // return(1 * Double(waterAmount))
// }
// }
var body: some View {
VStack (spacing: 5) {
Spacer()
HStack {
// Text("").padding(20)
Text("How many cups do you want to brew?")
Picker("", selection: $waterAmount) {
ForEach(1...15, id: \.self){
Text("\($0)")
}
}
// Spacer()
}.padding()
.overlay (
RoundedRectangle(cornerRadius: 16)
.stroke(Color("Custom Color"), lineWidth: 8)
)
// gif/image conditionals
if (brewModel == .frenchPress) {
LottieView(name: "frenchpress", loopMode: .loop)
} else if brewModel == .chemex {
LottieView(name: "pourover", loopMode: .loop)
} else if brewModel == .aeroPress {
LottieView(name: "aeropress", loopMode: .loop)
} else if brewModel == .mokaPot {
LottieView(name: "mokapot", loopMode: .loop)
} else if brewModel == .drip {
Image("Drip")
.resizable()
.scaledToFit()
}
// I would have more conditionals but testing with just these two for now
var testingCalcCond = Double
if (brewModel == .frenchPress)||(grindsSelection=="tbsp") {
testingCalcCond = (2.5 * Double(waterAmount))
} else if (brewModel == .frenchPress)||(grindsSelection=="grams") {
testingCalcCond = (16 * Double(waterAmount))
}
let formatted = String(format: "%.2f", testingCalcCond)
// let formatted = String(format: "%.2f", computeGrinds())
HStack {
Text("**\(formatted)**")
Picker("Select Grinds Units: ", selection: $grindsSelection, content: {
ForEach(grindOptions, id: \.self) {
Text($0)
}
}).onChange(of: grindsSelection) { _ in computeGrinds() }
Text("of coffee grinds needed")
}
.padding()
.overlay (
RoundedRectangle(cornerRadius: 16)
.stroke(Color("Custom Color"), lineWidth: 8)
)
}
Spacer()
}
}
struct Water_Previews: PreviewProvider {
static var previews: some View {
Water(brewModel: .drip)
}
}
}
*I'm using Xcode 13.2.1
*I'm using swiftUI
There are 2 aspects you want to think over:
1. How to update the result value based on the inputs.
Your result value is based on two inputs: brewModel and waterAmount. Both are #State vars and changed by a picker.
I changed your computeGrinds func to a computed property, because this will be automatically called when one of the two base values changes. Then there is no need for .onchange anymore, you can just use the var value – it will always be up to date.
2. recalculating from tbsp to grams.
This is more of a math thing: As I understand, for .frenchPress you need either 2.5 tbsp – or 16 grams per cup. So 1 tbsp = 16 / 2.5 = 6.4 grams. Once you know that you just have to go through the switch case once, and use the unitValue to recalculate. I integrated that too ;)
Here is my simplified code:
enum BrewModel {
case frenchPress
case chemex
case drip
case mokaPot
case aeroPress
}
struct ContentView: View {
#State var animationInProgress = true
#State var brewModel: BrewModel = .frenchPress
#State var waterAmount: Int = 1
#State var grindsSelection = "tbsp"
let grindOptions = ["tbsp", "grams"]
// computed var instead of func, does the same
var computeGrinds: Double {
// transforms tbsp = 1 to grams (= 6.4 ?)
var unitValue: Double = 1.0
if grindsSelection == "grams" {
unitValue = 6.4
}
switch brewModel {
case .frenchPress, .chemex:
return (2.5 * unitValue * Double(waterAmount))
case .drip :
return Double(2 * unitValue * Double(waterAmount))
case .mokaPot:
return Double(1 * unitValue * Double(waterAmount))
case .aeroPress:
return Double(1.6 * unitValue * Double(waterAmount))
}
}
var body: some View {
VStack (spacing: 5) {
HStack {
Text("How many cups do you want to brew?")
Picker("", selection: $waterAmount) {
ForEach(1...15, id: \.self){
Text("\($0)")
}
}
}
.padding()
.overlay (
RoundedRectangle(cornerRadius: 16)
.stroke(Color.brown, lineWidth: 8)
)
.padding(.bottom)
let formatted = String(format: "%.2f", computeGrinds)
HStack {
Text("**\(formatted)**")
Picker("Select Grinds Units: ", selection: $grindsSelection, content: {
ForEach(grindOptions, id: \.self) {
Text($0)
}
})
Text("of coffee grinds needed")
}
.padding()
.overlay (
RoundedRectangle(cornerRadius: 16)
.stroke(Color.brown, lineWidth: 8)
)
}
}
}

How to make pagination from inline buttons Telegram in Golang

I am using the library https://github.com/go-telegram-bot-api/telegram-bot-api
But I do not understand how I can implement the output of buttons and arrows. A lot of data with pagination comes from my api, but I don't understand how to make pages in telegrams. If someone gives an example, I will be very grateful!
This is perfectly implemented in python: https://pypi.org/project/python-telegram-bot-pagination/
But i need for Golang :(
As I checked there were no plugins or wrappers for telegram-bot-api package, so you're gonna have to handle this manually.
Suppose we have this data:
var data = []string{"DummyData1", "DummyData2", "DummyData3", "DummyData4", "DummyData5", "DummyData6", "DummyData7", "DummyData8", "DummyData9", "DummyData10"}
If we're going to show 2 items on each page for 10 items, we would have 5 pages:
var count = 2
var maxPages = len(data) / count // = 5
First we should have a function that calculates data slice and gives us the inlineKeyboardMarkup:
func DummyDataTextMarkup(currentPage, count int) (text string, markup tgbotapi.InlineKeyboardMarkup) {
text = strings.Join(data[currentPage*count:currentPage*count+count], "\n")
var rows []tgbotapi.InlineKeyboardButton
if currentPage > 0 {
rows = append(rows, tgbotapi.NewInlineKeyboardButtonData("Previous", fmt.Sprintf("pager:prev:%d:%d", currentPage, count)))
}
if currentPage < maxPages-1 {
rows = append(rows, tgbotapi.NewInlineKeyboardButtonData("Next", fmt.Sprintf("pager:next:%d:%d", currentPage, count)))
}
markup = tgbotapi.NewInlineKeyboardMarkup(rows)
return
}
Then there is going to be a function to send/edit the calculated data and keyboard by passing chatId, currentPage, count and messageId:
func SendDummyData(chatId int64, currentPage, count int, messageId *int) {
text, keyboard := DummyDataTextMarkup(currentPage, count)
var cfg tgbotapi.Chattable
if messageId == nil {
msg := tgbotapi.NewMessage(chatId, text)
msg.ReplyMarkup = keyboard
cfg = msg
} else {
msg := tgbotapi.NewEditMessageText(chatId, *messageId, text)
msg.ReplyMarkup = &keyboard
cfg = msg
}
bot.Send(cfg)
}
(Note: If we pass a messageId to this function it's going to edit our message with new data)
The next step is to have a CallbackQueryHandler to handle a user's click on inline buttons:
func CallbackQueryHandler(query *tgbotapi.CallbackQuery) {
split := strings.Split(query.Data, ":")
if split[0] == "pager" {
HandleNavigationCallbackQuery(query.Message.MessageID, split[1:]...)
return
}
}
func HandleNavigationCallbackQuery(messageId int, data ...string) {
pagerType := data[0]
currentPage, _ := strconv.Atoi(data[1])
itemsPerPage, _ := strconv.Atoi(data[2])
if pagerType == "next" {
nextPage := currentPage + 1
if nextPage < maxPages {
SendDummyData(chatId, nextPage, itemsPerPage, &messageId)
}
}
if pagerType == "prev" {
previousPage := currentPage - 1
if previousPage >= 0 {
SendDummyData(chatId, previousPage, itemsPerPage, &messageId)
}
}
}
(Note: The first function CallbackQueryHandler is a global callback handler that calls our desired callbackhandler by splitting its query with : and getting the handler's name, here it is pager as we have defined and its handler is called HandleNavigationCallbackQuery).
The last step would be to call CallbackQueryHandler in your update loop as well as sending the initial data to your desired chat:
var chatId = int64(0) // <--- Place Chat Id Here
SendDummyData(chatId, 0, 2, nil) // Send initial data
for update := range updates {
if update.CallbackQuery != nil {
CallbackQueryHandler(update.CallbackQuery)
continue
}
}
You can check the full example on my GitHub's gist here

How to declare an array of ui.NewEntry?

I am trying to create a GUI with a series of text entry fields:
package main
import ("github.com/andlabs/ui")
func main() {
ui.Main(makeMainWin)
}
func makeMainWin(){
var entlist = []ui.NewEntry //Error here. How to declare an array of ui.NewEntry?
var box = ui.NewVerticalBox()
for i,_ := range [5]int{} {
println(i)
box.Append(ui.NewEntry(), false)
}
var mainWindow = ui.NewWindow("Hello", 200, 100, false)
mainWindow.SetChild(box)
mainWindow.OnClosing( func (*ui.Window) bool {
ui.Quit(); return true } )
mainWindow.Show()
}
However, there is error on var entlist = []NewEntry
I am not able to create an array of NewEntry components. I have tried []ui.NewEntry, []*ui.NewEntry, []ui.NewEntry() and []*ui.NewEntry()
Where is the problem and how can it be solved? Thanks for your help.
ui.NewEntry returns *Entry, therefore your slice should be declared as:
var entlist []*ui.Entry

How to make NSSpellChecker work with NSDocument?

I have a core data / document-driven macOS app, using Swift and I struggle on combining the out-of-the-box spell checking API with an NSDocument (NSPersistentDocument in my case)
It took me more time than it should take, but this is what I got, mostly guided by this great answer:
class VTDocument: NSPersistentDocument, NSChangeSpelling {
[...]
private let spellchecker = SpellChecker()
#IBAction func showGuessPanel(_ sender: Any?){
spellchecker.startSpellCheck(nodes: Array(db.nodes), tag: 0)
}
#IBAction #objc func changeSpelling(_ sender: Any?){
spellchecker.replace(with: "Test")
}
This is leading me to see the NSSpellChecker.spellingPanel, correctly showing the word to correct. However, the changeSpelling function should be "called" by the panel but is never called. The above spellChecker is a simple wrapper around the NSSpellChecker that keeps the status between function calls.
The SpellChecker class looks like this.
import Cocoa
class SpellChecker {
let checker = NSSpellChecker.shared
let count: UnsafeMutablePointer<Int> = UnsafeMutablePointer<Int>.allocate(capacity: 1)
var nodes = Array<Node> ()
var nodeNr = 0
var stringPos = 0
var range: NSRange = NSRange()
func startSpellCheck(nodes: [Node], tag: Int ) {
self.nodes = nodes
nodeNr = 0
stringPos = 0
continueChecking()
}
func continueChecking(){
if nodes.count == 0 {
return
}
if nodeNr >= nodes.count {
checker.updateSpellingPanel(withMisspelledWord: "")
checker.spellingPanel.orderFront(self)
return
}
if let nodeText = nodes[nodeNr].label {
range = checker.checkSpelling(of: nodeText, startingAt: stringPos, language: nil, wrap: false, inSpellDocumentWithTag: 0, wordCount: count)
if count.pointee > 0 {
stringPos = range.lowerBound
checker.updateSpellingPanel(withMisspelledWord: nodeText[range])
checker.spellingPanel.orderFront(self)
return
}
}
nodeNr = nodeNr + 1
continueChecking()
}
func replace(with: String){
if let nodeText = nodes[nodeNr].label {
let text = nodeText as NSString
text.replacingCharacters(in: range, with: with)
nodes[nodeNr].label = text as String
}
}
}

Resources