Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

cojo2015

Alter Hase

  • »cojo2015« ist der Autor dieses Themas

Beiträge: 516

Wohnort: bei mir zu Hause

Beruf: Schüler

  • Private Nachricht senden

1

18.06.2016, 21:34

[C++ | SFML] Slider in Gui integrieren

Hallo,
mein Freund und ich versuchen ein Spiel zu programmieren. Zu jedem Spiel gehört natürlich u.a. ein Menü bzw. eine Gui. Unsere Gui basiert auf dem Composide-Pattern, so wie jede andere Gui auch. Das Problem, was wir jetzt haben, ist folgendes: Zu einem Slider gehört ja auch ein Thumb. Diesen Thumb soll man mit der Maus bewegen können, um den Wert zu ändern, den der Slider zurückgibt. Die Sache ist nur die, dass wir die Events von der Slider-Track auf den Thumb "umleiten" müssen, um diesen zu bewegen. Genau daran scheitern wir :(

PS: Der Slider ist ein Widget und kann dadurch keine weiteren Elemente aufnehmen. Ein WidgetContainer kann das. Ein Beispiel: Ein Fenster kann weitere Elemente aufnehmen (Button, CheckBox, Slider, ...) und ist damit ein WidgetContainer. Ein Slider, der sich im Fenster befindet, kann allerdings keine weiteren Elemente aufnehmen. Sonst könnte man dem Slider bspw. ein Fenster zuweisen, was aber kein Sinn ergibt.

Der Code:
[Header]

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#pragma once

#include <gui\widgets\Widget.h>
#include <gui\widgets\Button.h>

class Slider : public Widget
{
public:
    Slider(float width, float height);

    void update() override;

    void onMouseOver(sf::Event event) override;
    void onMouseOut(sf::Event event) override;
    void onMouseUp(sf::Event event) override;
    void onMouseDown(sf::Event event) override;
    void onMouseReleased(sf::Event event) override;

    int getValue();

private:
    void draw(sf::RenderTarget& target, sf::RenderStates states) const override;

    sf::RectangleShape* _slider;
    Button* _thumb;
    sf::Color _sliderstates[3];

    bool _hover, _clicked, _released;
    int _value;
};


[Quelldatei]

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <gui\widgets\Slider.h>

Slider::Slider(float width, float height)
: Widget(width, height)
, _slider(new sf::RectangleShape(sf::Vector2f(width, height)))
, _thumb(new Button(20,20))
, _hover(false)
, _clicked(false)
, _released(false)
, _value(50)
{
    _sliderstates[0] = sf::Color(sf::Color::Red);
    _sliderstates[1] = sf::Color(sf::Color::Blue);
    _sliderstates[2] = sf::Color(sf::Color::Green);

    _thumb->setPosition(sf::Vector2f(_slider->getPosition().x, _slider->getPosition().y + height/2));
}
/////////////////////////////////////////////////////////////////////////////////////
void Slider::update()
{
    std::cout << "Slidervalue: " << _value << "\n";
    _thumb->update();
}
/////////////////////////////////////////////////////////////////////////////////////
void Slider::onMouseOver(sf::Event event)
{
    _hover = true;
    _thumb->onMouseOver(event);
}
/////////////////////////////////////////////////////////////////////////////////////
void Slider::onMouseOut(sf::Event event)
{
    _hover = false;
    _thumb->onMouseOut(event);
}
/////////////////////////////////////////////////////////////////////////////////////
void Slider::onMouseUp(sf::Event event)
{
    _clicked = false;
    _thumb->onMouseUp(event);
}
/////////////////////////////////////////////////////////////////////////////////////
void Slider::onMouseDown(sf::Event event)
{
    _clicked = true;
    _thumb->onMouseDown(event);
    _thumb->setPosition(this->globalToLocal(event).x, _slider->getPosition().y + this->getHeight()/2);
}
/////////////////////////////////////////////////////////////////////////////////////
void Slider::onMouseReleased(sf::Event event)
{
    _thumb->onMouseReleased(event);
}
/////////////////////////////////////////////////////////////////////////////////////
int Slider::getValue()
{
    return _value;
}
/////////////////////////////////////////////////////////////////////////////////////
void Slider::draw(sf::RenderTarget & target, sf::RenderStates states) const
{
    if (_hover && !_clicked)
    {
        _slider->setFillColor(_sliderstates[1]);
    }
    else if (_clicked)
    {
        _slider->setFillColor(_sliderstates[2]);
    }
    else
    {
        _slider->setFillColor(_sliderstates[0]);
    }

    target.draw(*_slider, states);
    target.draw(*_thumb, states);
}


Vielen Dank im Voraus :D

2

20.06.2016, 08:01

Wenn die Maus über dem Slider ist und Maustaste gedrückt wird, die Mausposition und abstand Sliderschieber <-> Maus zwischenspeichern. Im neuen Frame ist die Differenz alte Mausposition zu neue Mausposition plus abstand Sliderschieber <-> Maus die neue Position vom Sliderschieber.
Das sollte soweit ich den Code gesehen habe problemlos zu integrieren sein mit den vorhandenen Events.
Wer aufhört besser werden zu wollen hört auf gut zu sein!

aktuelles Projekt:Rickety Racquet

cojo2015

Alter Hase

  • »cojo2015« ist der Autor dieses Themas

Beiträge: 516

Wohnort: bei mir zu Hause

Beruf: Schüler

  • Private Nachricht senden

3

27.06.2016, 15:58

Irgendwie klappt das immer noch nicht so, wie ich das will. Ich kann den Thumb nur bewegen, wenn die Maus sich genau über dem Slider befindet, und das nervt ^^ Aber wie bekomme ich es hin, dass ich Thumb und Slider "vereine"?

4

28.06.2016, 13:56

Irgendwie klappt das immer noch nicht so, wie ich das will. Ich kann den Thumb nur bewegen, wenn die Maus sich genau über dem Slider befindet, und das nervt ^^ Aber wie bekomme ich es hin, dass ich Thumb und Slider "vereine"?

Wie wäre es sich zu merken das der Slider gerade am "Hacken" hängt und so lange die Prüfung ob Maus über Slider ist aussetzen?
Wer aufhört besser werden zu wollen hört auf gut zu sein!

aktuelles Projekt:Rickety Racquet

Sacaldur

Community-Fossil

Beiträge: 2 301

Wohnort: Berlin

Beruf: FIAE

  • Private Nachricht senden

5

28.06.2016, 16:03

Vielleicht erhält der Thumb die MouseMove events, was bedeuten würde, dass Mausbewegungen aus dem Thumb heraus nicht als Bewegung an diesen weitergeleitet werden würden. In dem Code oben sehe ich allerdings auch nicht, an welcher Stelle eben genau dieses Verhalten (Auswerten von MouseMove) implementiert wird...
Spieleentwickler in Berlin? (Thema in diesem Forum)
---
Es ist ja keine Schande etwas falsch zu machen, als Programmierer tu ich das täglich, [...].

6

28.06.2016, 16:14

Das Problem besteht darin das der hittest (also die Prüfung ob die Maus drüber ist) nur anhand der Daten des vererbten Widgets stattfindet. Das heißt wenn man z.B "slider.setposition(...)" aufrufen würde, dann würde sich das auf die Transformmatrix des Widget beziehen und nicht auf die des eigentlichen Sliders(also Track und Thumb). Das Thema ist leider so abstrakt behindert, dass ich nicht in der Lage bin es genau beschreiben zu könnnen. Es ist wahrscheinlich einfach wieder nur ein Verständnisproblem welches uns hier diesen Ärger beschert.

Hier mal die Slider.h

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#ifndef SLIDER_H
#define SLIDER_H

#include <gui/widgets/Widget.h>

namespace gui
{
    class Slider : public Widget
    {
    public:
        Slider(float width, float height);

        void update() override;

        void setValue(int percent);
        

        int getValue();

    private:
        void draw(sf::RenderTarget& target, sf::RenderStates states) const override;

        void updatePosition(sf::Event& ev);
        void calculateValue();

                //Callback functions
        void onWidgetReleased(sf::Event& ev) override;
        void onWidgetPressed(sf::Event& ev) override;
        void onWidgetEntered(sf::Event& ev) override;
        void onWidgetLeft(sf::Event& ev) override;
        void onWidgetMoved(sf::Event& ev) override;

        sf::RectangleShape _track, _thumb;
        int _percentValue;
    };
}
#endif // !SLIDER_H


Und die dazugehörige Slider.cpp

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#include <gui\widgets\Slider.h>

namespace gui
{
    /////////////////////////////////////////////////////////////////////////////////////
    Slider::Slider(float width, float height)
        : Widget(width, height)
        , _percentValue(0)
    {
        _track.setSize(sf::Vector2f(width, height));
        _track.setFillColor(sf::Color::Black);
        _thumb.setSize(sf::Vector2f(this->getWidth()/8, this->getWidth()/8));
        _thumb.setOrigin(sf::Vector2f(_thumb.getSize().x/2, _thumb.getSize().y/2));

        _thumb.setFillColor(sf::Color::Green);

                //Hinzufügen der Callbacks gebunden an Events
        this->addEventListener(sf::Event::MouseButtonReleased, std::bind(&Slider::onWidgetReleased, this, std::placeholders::_1));
        this->addEventListener(sf::Event::MouseButtonPressed, std::bind(&Slider::onWidgetPressed, this, std::placeholders::_1));
        this->addEventListener(sf::Event::MouseEntered, std::bind(&Slider::onWidgetEntered, this, std::placeholders::_1));
        this->addEventListener(sf::Event::MouseLeft, std::bind(&Slider::onWidgetLeft, this, std::placeholders::_1));
        this->addEventListener(sf::Event::MouseMoved, std::bind(&Slider::onWidgetMoved, this, std::placeholders::_1));

        this->setType(WidgetType::Slider);
        this->setDraggable(true);
    }
    /////////////////////////////////////////////////////////////////////////////////////
    void Slider::update()
    {
    }
    /////////////////////////////////////////////////////////////////////////////////////
    void Slider::updatePosition(sf::Event& ev)
    {
                //Diese Funktion ist dazu gedacht den Thumb auf der Track zu bewegen
        //if (_thumb.getGlobalBounds().contains(this->globalToLocal(ev.mouseMove.x, ev.mouseMove.y)))
        //{
        //  std::cout << "ok\n";
        //}
        float diffX = this->globalToLocal(ev.mouseMove.x, ev.mouseMove.y).x - _track.getPosition().x;
        _thumb.setPosition(sf::Vector2f(_track.getPosition().x + diffX, _track.getGlobalBounds().height/2));

        if (_thumb.getPosition().x > _track.getGlobalBounds().width - _thumb.getGlobalBounds().width/2)
        {
            _thumb.setPosition(sf::Vector2f(_track.getGlobalBounds().width - _thumb.getGlobalBounds().width / 2, _thumb.getPosition().y));
        }
        else if (_thumb.getPosition().x < _thumb.getGlobalBounds().width/2)
        {
            _thumb.setPosition(sf::Vector2f(_thumb.getGlobalBounds().width / 2, _thumb.getPosition().y));
        }
    }
    /////////////////////////////////////////////////////////////////////////////////////
    void Slider::onWidgetReleased(sf::Event & ev)
    {
        this->setClicked(false);
    }
    /////////////////////////////////////////////////////////////////////////////////////
    void Slider::onWidgetPressed(sf::Event & ev)
    {
        this->setClicked(true);
    }
    /////////////////////////////////////////////////////////////////////////////////////
    void Slider::onWidgetEntered(sf::Event & ev)
    {
        this->setHovered(true);
        _track.setFillColor(sf::Color::Blue);
    }
    /////////////////////////////////////////////////////////////////////////////////////
    void Slider::onWidgetLeft(sf::Event & ev)
    {
        this->setHovered(false);
        _track.setFillColor(sf::Color::Black);
    }
    /////////////////////////////////////////////////////////////////////////////////////
    void Slider::onWidgetMoved(sf::Event & ev)
    {
        updatePosition(ev);
        //calculateValue();             
    }
    /////////////////////////////////////////////////////////////////////////////////////
    void Slider::calculateValue()
    {
        static float trackLenght = _track.getGlobalBounds().width - this->getWidth();
        float diffX = this->getPosition().x - _track.getPosition().x;
        _percentValue = 100 / trackLenght * diffX;
        std::cout << trackLenght << " - " << _percentValue << " - " << diffX <<"\n";
    }
    /////////////////////////////////////////////////////////////////////////////////////
    void Slider::setValue(int percent)
    {
        _percentValue = percent;
    }
    /////////////////////////////////////////////////////////////////////////////////////
    int Slider::getValue()
    {
        return _percentValue;
    }
    /////////////////////////////////////////////////////////////////////////////////////
    void Slider::draw(sf::RenderTarget & target, sf::RenderStates states) const
    {
                //Thumb und Track werden nach den States gezeichnet. (Die States stammen von übergeordneten Widgets)
        target.draw(_track, states);
        target.draw(_thumb, states);
    }
    /////////////////////////////////////////////////////////////////////////////////////
}


Die Sliderschiene(_track) und der Sliderkopf(_thumb) werden von den sf::RenderStates transformiert.
Hier noch der Hittest welcher prüft ob man mit der Maus über dem Widget ist. (Die Funktionen stammen aus der Basisklasse aller Gui Elemente: Widget.cpp)

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    bool Widget::hittest(float mouseX, float mouseY)
    {
        sf::Vector2f localMousePos = globalToLocal(mouseX, mouseY);

         //std::cout << "X: " << localMousePos .x << " | Y: " << localMousePos.y << "\n";

        return (localMousePos.x > 0 && localMousePos.x < _width && localMousePos.y > 0 && localMousePos.y < _height);
    }
    /////////////////////////////////////////////////////////////////////////////////////
    sf::Vector2f Widget::globalToLocal(float x, float y)
    {       //Die Funktion wandelt den globalen space in den lokalen um. (um eine relative Mausposition zu schaffen)
        sf::Transform mtx = getConcatenatedMatrix();
         mtx = mtx.getInverse();

        return mtx.transformPoint(x, y);
    }
    /////////////////////////////////////////////////////////////////////////////////////
    sf::Transform Widget::getConcatenatedMatrix()
    {
                //Es werden solange die Tramsformmatrizen aufmultipliziert bis das Child kein Parent mehr hat.
        while (this->getParent() != nullptr)
        {
            return this->getTransform() * _parent->getConcatenatedMatrix();
        }
        return this->getTransform();
    }


Die hittest Funktion wird dann übers Eventsystem aufgerufen (z.B wenn eine Mausbewegung stattgefunden hat).
Die Gui ist nach dem Composite pattern aufgebaut und besagt das es Elemente gibt die weitere Elemente von gleicher Art aufnehmen können, und welche die es nicht können.
Bei der Gui ist es einmal das Widget welches die Elemente sind die keine weiteren Elmente aufnehmen können(z.B Button, Slider), und die WidgetContainer die es können(z.B Fenster).
Das Problem beim Slider ist halt nun dass es kein WidgetContainer sein kann, da man ihm sonst ja auch ein Fenster zuordnen könnte. Was aber gehen würde wäre wenn der Slider ein WidgetContainer wäre, sodass man ihm ein Button als Child gibt, welches den Thumb repräsentiert. Die momentane Lage sieht dann halt so aus dass wir für die Track und den Thumb einfach sf::RectangleShape`s nehmen. Jetzt muss man halt selber definieren was der Slider nun ist, entweder Thumb oder Track. Dies muss man vorab klären da man ja prüfen muss ob man überm Slider ist (Also über der Track, oder über dem Thumb ?). Meine Idee war es das Widget als Thumb zu behandeln, also Widget Position gleich der Thumb Position, sowie Größe, Rotation usw... Aber irgendwie will das ganze auch nich so.

7

29.06.2016, 17:53

Wenn euch der Hittest aus der BasisKlasse stört oder Probleme macht, dann überschreibt ihn in der Slider-Klasse.

Habe allerdings auch noch nicht zu 100% verstanden wo genau das Problem liegt.
Wer aufhört besser werden zu wollen hört auf gut zu sein!

aktuelles Projekt:Rickety Racquet

8

30.06.2016, 08:16

Die Idee ist sogar gar nicht mal so schlecht, nur müsste ich dann die Funktion "getConcatenatedMatrix()" überschreiben. Denn dort liegt nämlich die eigentliche Rechnung. Ich multipliziere in der Funktion rekursiv die Transformmatrizen der Widgets auf (um relative Positionen zu schaffen). Das heißt was ich machen müsste wäre die "getConcatenatedMatrix()" Funktion zu überschreiben, und anstatt "this->getTransform()" irgendwie einen Parameter zu übergeben der dann die Rechnung verändert (z.B die Transformmatrix von der "_track" (sf::RectangleShape) Variable). Aber irgendwie gefällt mir diese Lösung noch nicht ganz.

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Sceiwen« (30.06.2016, 10:26)


Sacaldur

Community-Fossil

Beiträge: 2 301

Wohnort: Berlin

Beruf: FIAE

  • Private Nachricht senden

9

30.06.2016, 13:56

Grundsätzlich wäre das vorgehen so: Man klickt auf den Thumb. Daraufhin registriert man sich für globale MouseMove-Events (soll heißen: man bekommt das Event, auch wenn egal wo die Maus sich währenddessen auf ddem Bildschirm befindet). Sobald sich die Maus bewegt, wird die Differenz zwischen neuer und vorheriger Position ermittelt, woraus die Thumb-Position und daraus wieder der Slider-Wert bestimmt werden. Sobald die Maustaste losgelassen wird, kann die Registrierung am Event wieder aufgehoben werden.

Schaue einfach, in wie weit sich deine Vorgehensweise davon unterscheidet und passe es daran an. Solange nicht ausreichend Informationen über die bisherige Implementierung vorhanden sind (und bei manchen Methoden vermute ich, dass sie falsch benannt wurden), kann ich bspw. nicht sagen, was du ändern müsstest, um zu dieser Vorgehensweise zu kommen.
Spieleentwickler in Berlin? (Thema in diesem Forum)
---
Es ist ja keine Schande etwas falsch zu machen, als Programmierer tu ich das täglich, [...].

Werbeanzeige